优化电子凭证核销码生成

This commit is contained in:
yml2213
2026-07-07 16:36:07 +08:00
parent 59f6f5ee1e
commit 2471df0086
2 changed files with 122 additions and 29 deletions
@@ -1,3 +1,5 @@
import crypto from 'node:crypto'
import { query } from '../db/client.js'
import type {
KuaishouIndustryVoucherUpdatePatch,
@@ -11,16 +13,42 @@ type VoucherPatchColumn = {
cast?: string
}
const VOUCHER_CODE_PREFIX = 'KSV'
const VOUCHER_CODE_RANDOM_BYTES = 10
const VOUCHER_CODE_MAX_GENERATE_ATTEMPTS = 5
const CROCKFORD_BASE32_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
export async function upsertKuaishouIndustryVoucher(
input: KuaishouIndustryVoucherUpsertInput,
): Promise<KuaishouIndustryVoucherRow | null> {
for (let attempt = 1; attempt <= VOUCHER_CODE_MAX_GENERATE_ATTEMPTS; attempt += 1) {
try {
return await insertKuaishouIndustryVoucher(input, generateKuaishouIndustryVoucherCode())
} catch (error) {
if (attempt < VOUCHER_CODE_MAX_GENERATE_ATTEMPTS && isVoucherCodeUniqueViolation(error)) {
continue
}
throw error
}
}
return null
}
export function generateKuaishouIndustryVoucherCode(
randomBytes: (size: number) => Uint8Array = crypto.randomBytes,
): string {
return `${VOUCHER_CODE_PREFIX}${encodeCrockfordBase32(randomBytes(VOUCHER_CODE_RANDOM_BYTES))}`
}
async function insertKuaishouIndustryVoucher(
input: KuaishouIndustryVoucherUpsertInput,
voucherCode: string,
): Promise<KuaishouIndustryVoucherRow | null> {
const result = await query<KuaishouIndustryVoucherRow>(
`
WITH next_id AS (
SELECT nextval(pg_get_serial_sequence('kuaishou_industry_vouchers', 'id')) AS id
)
INSERT INTO kuaishou_industry_vouchers (
id,
voucher_code,
oid,
order_id,
@@ -38,9 +66,7 @@ export async function upsertKuaishouIndustryVoucher(
created_at,
updated_at
)
SELECT
next_id.id,
next_id.id::text,
VALUES (
$1,
$2,
$3,
@@ -50,13 +76,14 @@ export async function upsertKuaishouIndustryVoucher(
$7,
$8,
$9,
$10::jsonb,
$11,
$10,
$11::jsonb,
$12,
$13::jsonb,
$14,
$15
FROM next_id
$13,
$14::jsonb,
$15,
$16
)
ON CONFLICT (oid, unit_index) DO UPDATE
SET
token = CASE
@@ -72,6 +99,7 @@ export async function upsertKuaishouIndustryVoucher(
RETURNING *
`,
[
voucherCode,
input.oid,
normalizeNullableId(input.orderId),
normalizeNullableId(input.taskId),
@@ -135,7 +163,8 @@ export async function findKuaishouIndustryVoucherByCode(
return null
}
const params: unknown[] = [normalizedCode]
const candidateCodes = Array.from(new Set([normalizedCode, normalizedCode.toUpperCase()]))
const params: unknown[] = [candidateCodes]
const oidFilter = normalizedOid ? 'AND oid = $2' : ''
if (normalizedOid) {
params.push(normalizedOid)
@@ -145,7 +174,7 @@ export async function findKuaishouIndustryVoucherByCode(
`
SELECT *
FROM kuaishou_industry_vouchers
WHERE voucher_code = $1
WHERE voucher_code = ANY($1::text[])
${oidFilter}
LIMIT 1
`,
@@ -252,6 +281,42 @@ function normalizeVoucherPatchColumns(
return columns
}
function encodeCrockfordBase32(bytes: Uint8Array): string {
let bits = 0
let value = 0
let output = ''
for (const byte of bytes) {
value = (value << 8) | byte
bits += 8
while (bits >= 5) {
output += CROCKFORD_BASE32_ALPHABET[(value >>> (bits - 5)) & 31] || ''
bits -= 5
}
}
if (bits > 0) {
output += CROCKFORD_BASE32_ALPHABET[(value << (5 - bits)) & 31] || ''
}
return output
}
function isVoucherCodeUniqueViolation(error: unknown): boolean {
const current =
error && typeof error === 'object'
? (error as { code?: unknown; constraint?: unknown; detail?: unknown })
: {}
if (String(current.code || '') !== '23505') {
return false
}
const marker = `${String(current.constraint || '')} ${String(current.detail || '')}`
return marker.includes('voucher_code')
}
function normalizeNullableId(value: unknown): number | null {
if (value === null || value === undefined || value === '') {
return null