feat(server): 完成 P2 后端能力(支付幂等、委派回收、OCR异步、审计)
这个提交包含在:
@@ -0,0 +1,103 @@
|
||||
const tencentcloud = require("tencentcloud-sdk-nodejs");
|
||||
|
||||
function normalizeDate(value) {
|
||||
if (!value || typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const compact = value.trim();
|
||||
if (!compact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const replaced = compact.replace(/年|\//g, "-").replace(/月/g, "-").replace(/日/g, "");
|
||||
const matched = replaced.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
|
||||
if (!matched) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [, year, month, day] = matched;
|
||||
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function toNumberString(value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value.toFixed(2);
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stripped = value.replace(/[,¥\s]/g, "").trim();
|
||||
if (!stripped) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const num = Number(stripped);
|
||||
if (!Number.isFinite(num)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return num.toFixed(2);
|
||||
}
|
||||
|
||||
function normalizeString(value) {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = value.trim();
|
||||
return normalized || null;
|
||||
}
|
||||
|
||||
function createTencentOcrClient(config) {
|
||||
if (!config.tencentSecretId || !config.tencentSecretKey) {
|
||||
throw new Error("Tencent OCR credentials are missing");
|
||||
}
|
||||
|
||||
const OcrClient = tencentcloud.ocr.v20181119.Client;
|
||||
return new OcrClient({
|
||||
credential: {
|
||||
secretId: config.tencentSecretId,
|
||||
secretKey: config.tencentSecretKey,
|
||||
},
|
||||
region: config.tencentOcrRegion,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
timeout: config.ocrTimeoutMs,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeVatInvoiceResult(result) {
|
||||
return {
|
||||
invoiceCode: normalizeString(result?.InvoiceCode),
|
||||
invoiceNumber: normalizeString(result?.InvoiceNum),
|
||||
invoiceDate: normalizeDate(result?.InvoiceDate),
|
||||
amount: toNumberString(result?.AmountWithoutTax),
|
||||
taxAmount: toNumberString(result?.TaxAmount),
|
||||
totalAmount: toNumberString(result?.TotalAmount ?? result?.AmountInFiguers),
|
||||
sellerName: normalizeString(result?.SellerName),
|
||||
buyerName: normalizeString(result?.BuyerName),
|
||||
raw: result ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
async function runTencentVatOcr(config, imageBase64) {
|
||||
const client = createTencentOcrClient(config);
|
||||
const result = await client.VatInvoiceOCR({
|
||||
ImageBase64: imageBase64,
|
||||
});
|
||||
|
||||
return normalizeVatInvoiceResult(result);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runTencentVatOcr,
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户