弱网核销功能

This commit is contained in:
2026-07-12 12:00:55 +08:00
parent de01d36cdb
commit 06b1cb22e0
24 changed files with 1434 additions and 42 deletions
+26
View File
@@ -7,6 +7,9 @@ import {
allocateBenefitCoupons,
calcBenefitSummary,
orderTabToStatuses,
classifyRedeemClientError,
generateRedeemPendingNo,
REDEEM_WEAKNET_FAIL_THRESHOLD,
} from './index';
describe('calcBenefitAmount', () => {
@@ -120,3 +123,26 @@ describe('calcBenefitSummary', () => {
});
});
});
describe('classifyRedeemClientError', () => {
it('treats 5xx and network messages as NETWORK', () => {
expect(classifyRedeemClientError({ status: 502 })).toBe('NETWORK');
expect(classifyRedeemClientError({ message: 'Failed to fetch' })).toBe('NETWORK');
expect(classifyRedeemClientError({ message: '网络超时' })).toBe('NETWORK');
});
it('treats business messages as BUSINESS', () => {
expect(classifyRedeemClientError({ status: 400, message: '核销码无效或已过期' })).toBe('BUSINESS');
expect(classifyRedeemClientError({ message: '余额不足' })).toBe('BUSINESS');
});
});
describe('generateRedeemPendingNo', () => {
it('uses RP prefix', () => {
expect(generateRedeemPendingNo().startsWith('RP')).toBe(true);
});
it('exports weaknet threshold of 5', () => {
expect(REDEEM_WEAKNET_FAIL_THRESHOLD).toBe(5);
});
});
+30
View File
@@ -111,6 +111,36 @@ export function generateRedeemNo(): string {
return `RD${Date.now()}${Math.floor(Math.random() * 1000)}`;
}
export function generateRedeemPendingNo(): string {
return `RP${Date.now()}${Math.floor(Math.random() * 1000)}`;
}
/** 弱网核销:网络类失败达到该次数后触发兜底 */
export const REDEEM_WEAKNET_FAIL_THRESHOLD = 5;
export type RedeemErrorClass = 'NETWORK' | 'BUSINESS';
/** 门店端/服务端共用:区分网络类与业务类核销错误(仅 NETWORK 计入 5 次) */
export function classifyRedeemClientError(input: {
status?: number | null;
message?: string | null;
}): RedeemErrorClass {
const status = input.status;
const message = (input.message ?? '').toLowerCase();
if (status != null && status >= 500) return 'NETWORK';
if (status === 0 || status === 408 || status === 429 || status === 502 || status === 503 || status === 504) {
return 'NETWORK';
}
if (
/failed to fetch|network|timeout|timed out|econnreset|econnrefused|etimedout|abort|offline|连接|网络|超时/.test(
message,
)
) {
return 'NETWORK';
}
return 'BUSINESS';
}
export function orderTabToStatuses(tab: string): string[] | undefined {
switch (tab) {
case 'pending_pay':