import { findKuaishouIndustryVoucherByCode, listKuaishouIndustryVouchersByOid, listKuaishouIndustryVouchersByTaskId, listKuaishouIndustryVouchersForAdmin, updateKuaishouIndustryVoucherByCode, type KuaishouIndustryVoucherAdminListQuery, } from '../../repositories/kuaishou-industry-voucher-repo.js' import { getOrderById } from '../../repositories/order-repo.js' import { getTaskById } from '../../repositories/task-repo.js' import { createHttpError } from '../../utils/http.js' import { maskSecret } from '../../utils/masking.js' import { checkKuaishouIndustryEticketAvailable } from '../platforms/kuaishou-industry/check-available-service.js' import { resendKuaishouIndustryVoucherSendCallback } from '../platforms/kuaishou-industry/send-code-service.js' import { consumeKuaishouIndustryVoucher } from '../platforms/kuaishou-industry/voucher-service.js' import { approveKuaishouIndustryRefund, disagreeKuaishouIndustryRefund, listKuaishouIndustryRefunds, } from '../platforms/kuaishou-industry/refund-service.js' import { reverseKuaishouIndustryCallback } from '../platforms/kuaishou-industry/reverse-callback-service.js' import type { KuaishouIndustryOpenApiCallResult } from '../platforms/kuaishou-industry/openapi-client.js' import type { KuaishouIndustryVoucherRow, TaskRow } from '../../types/repository/rows.js' type JsonObject = Record export async function listAdminKuaishouIndustryVouchers( input: KuaishouIndustryVoucherAdminListQuery = {}, ) { const result = await listKuaishouIndustryVouchersForAdmin(input) return { items: result.items.map(mapAdminKuaishouIndustryVoucher), pagination: { page: result.page, pageSize: result.pageSize, total: result.total, }, } } export async function listAdminKuaishouIndustryRefunds(input: JsonObject = {}) { assertRequired(input.beginTime, '开始时间未填写') assertRequired(input.endTime, '结束时间未填写') return mapOpenApiResult(await listKuaishouIndustryRefunds(input)) } export async function approveAdminKuaishouIndustryRefund(input: JsonObject = {}) { assertRequired(input.refundId, '退款单编号未填写') assertRequired(input.refundAmount, '退款金额未填写') return mapOpenApiResult(await approveKuaishouIndustryRefund(input)) } export async function disagreeAdminKuaishouIndustryRefund(input: JsonObject = {}) { assertRequired(input.refundId, '退款单编号未填写') assertRequired(input.sellerDisagreeReason, '拒绝原因未填写') assertRequired(input.sellerDisagreeDesc, '拒绝说明未填写') assertRequired(input.status, '退款单当前状态未填写') assertRequired(input.negotiateStatus, '协商状态未填写') return mapOpenApiResult(await disagreeKuaishouIndustryRefund(input)) } export async function checkAdminKuaishouIndustryVoucherAvailable(input: JsonObject = {}) { const voucher = await resolveOptionalVoucher(input) const payload = buildVoucherOpenApiPayload(input, voucher) assertRequired(payload.sellerId, '卖家编号未填写') assertRequired(payload.eticketType || payload.bizTypeCode, '电子凭证类型未填写') assertEtickets(payload.etickets, '电子凭证列表未填写') return mapOpenApiResult(await checkKuaishouIndustryEticketAvailable(payload)) } export async function reverseAdminKuaishouIndustryVoucher(input: JsonObject = {}) { const voucher = await resolveOptionalVoucher(input) const payload = buildVoucherOpenApiPayload(input, voucher) assertRequired(payload.oid, '订单号未填写') assertEtickets(payload.etickets, '冲正券码列表未填写') const result = await reverseKuaishouIndustryCallback(payload) let updatedVoucher: KuaishouIndustryVoucherRow | null = null if (result.success && voucher) { updatedVoucher = await updateKuaishouIndustryVoucherByCode(voucher.voucher_code, { status: 'UNUSED', consumeSerialNum: '', consumeDetailsJson: [], consumedAt: null, destroyedAt: null, updatedAt: new Date().toISOString(), }) } return { ...mapOpenApiResult(result), voucher: updatedVoucher ? mapAdminKuaishouIndustryVoucher(updatedVoucher) : null, } } export async function consumeAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) { const voucher = await resolveRequiredVoucher(input) const task = voucher.task_id ? await getTaskById(voucher.task_id) : null const consumeInput: Parameters[1] = { source: 'admin_tool_manual_consume', token: String(input.token || voucher.token || '').trim(), consumeType: String(input.consumeType || 'delivery').trim() || 'delivery', consumeTime: Date.now(), ...(input.serialNum ? { serialNum: String(input.serialNum).trim() } : {}), ...(input.eticketType ? { eticketType: String(input.eticketType).trim() } : {}), ...(input.storeName ? { storeName: String(input.storeName).trim() } : {}), ...(input.storeAddress ? { storeAddress: String(input.storeAddress).trim() } : {}), ...(input.expressCode ? { expressCode: String(input.expressCode).trim() } : {}), ...(input.expressNo ? { expressNo: String(input.expressNo).trim() } : {}), } if (task) { consumeInput.task = task } const result = await consumeKuaishouIndustryVoucher(voucher, consumeInput) if (!result.ok || !result.voucher) { throw createHttpError(result.errorMessage || '电子凭证核销失败', { statusCode: 409, errorCode: 'admin_kuaishou_industry_consume_failed', }) } return { success: true, voucher: mapAdminKuaishouIndustryVoucher(result.voucher), task: task ? mapTaskReference(task) : null, } } export async function resendAdminKuaishouIndustryVoucherCode(input: JsonObject = {}) { const voucher = await resolveRequiredVoucher(input) const order = voucher.order_id ? await getOrderById(voucher.order_id) : null const result = await resendKuaishouIndustryVoucherSendCallback({ voucherCode: voucher.voucher_code, oid: voucher.oid, preferredTotalGoodsValue: Number(order?.total_amount || 0) || 0, }) return { success: result.success, response: 'response' in result ? result.response || null : null, error: result.error || '', voucher: result.voucher ? mapAdminKuaishouIndustryVoucher(result.voucher) : null, } } function buildVoucherOpenApiPayload( input: JsonObject, voucher: KuaishouIndustryVoucherRow | null, ): JsonObject { const etickets = normalizeAdminEtickets(input.etickets) const voucherCode = String(voucher?.voucher_code || input.voucherCode || '').trim() return { ...input, sellerId: String(input.sellerId || voucher?.seller_id || '').trim(), oid: String(input.oid || input.orderId || voucher?.oid || '').trim(), orderId: String(input.orderId || input.oid || voucher?.oid || '').trim(), token: String(input.token || voucher?.token || '').trim(), serialNum: String(input.serialNum || voucher?.consume_serial_num || '').trim(), etickets: etickets.length > 0 ? etickets : voucherCode ? [{ id: voucherCode, code: voucherCode, num: 1 }] : [], } } async function resolveOptionalVoucher(input: JsonObject): Promise { const voucherCode = String(input.voucherCode || '').trim() if (!voucherCode) { return null } const voucher = await findKuaishouIndustryVoucherByCode(voucherCode, String(input.oid || input.orderId || '').trim()) if (!voucher) { throw createHttpError('电子凭证不存在', { statusCode: 404, errorCode: 'admin_kuaishou_industry_voucher_not_found', }) } return voucher } async function resolveRequiredVoucher(input: JsonObject): Promise { const voucher = await resolveOptionalVoucher(input) if (voucher) { return voucher } const taskId = Number(input.taskId || 0) if (Number.isFinite(taskId) && taskId > 0) { const vouchers = await listKuaishouIndustryVouchersByTaskId(Math.trunc(taskId)) return resolveSingleVoucher(vouchers, '当前任务没有关联电子凭证', '当前任务关联多个电子凭证,请指定券码') } const oid = String(input.oid || input.orderId || '').trim() if (oid) { const vouchers = await listKuaishouIndustryVouchersByOid(oid) return resolveSingleVoucher(vouchers, '当前订单没有关联电子凭证', '当前订单关联多个电子凭证,请指定券码') } throw createHttpError('请填写券码、任务 ID 或订单号', { statusCode: 400, errorCode: 'admin_kuaishou_industry_voucher_selector_missing', }) } function resolveSingleVoucher( vouchers: KuaishouIndustryVoucherRow[], missingMessage: string, multipleMessage: string, ): KuaishouIndustryVoucherRow { if (vouchers.length === 0) { throw createHttpError(missingMessage, { statusCode: 404, errorCode: 'admin_kuaishou_industry_voucher_not_found', }) } if (vouchers.length > 1) { throw createHttpError(multipleMessage, { statusCode: 409, errorCode: 'admin_kuaishou_industry_voucher_ambiguous', }) } return vouchers[0] as KuaishouIndustryVoucherRow } function mapOpenApiResult(result: KuaishouIndustryOpenApiCallResult) { return { success: result.success, response: result.response || null, error: result.error || '', request: sanitizeOpenApiRequest(result.request), durationMs: result.durationMs || 0, httpStatus: result.httpStatus || 0, skippedReason: result.skippedReason || '', } } function sanitizeOpenApiRequest(request: JsonObject | undefined): JsonObject | null { if (!request) { return null } return { ...request, access_token: maskSecret(request.access_token), sign: maskSecret(request.sign), } } function mapAdminKuaishouIndustryVoucher(voucher: KuaishouIndustryVoucherRow) { return { id: voucher.id, voucherCode: voucher.voucher_code, oid: voucher.oid, orderId: voucher.order_id, taskId: voucher.task_id, unitIndex: voucher.unit_index, sellerId: voucher.seller_id, tokenMasked: maskSecret(voucher.token), status: voucher.status, validStartTime: Number(voucher.valid_start_time || 0) || 0, validEndTime: Number(voucher.valid_end_time || 0) || 0, consumeSerialNum: voucher.consume_serial_num, consumeDetails: parseJsonArray(voucher.consume_details_json), consumedAt: voucher.consumed_at, destroyedAt: voucher.destroyed_at, sendCallbackStatus: voucher.send_callback_status, sendCallbackAttemptCount: voucher.send_callback_attempt_count, sendCallbackLastError: voucher.send_callback_last_error, sendCallbackSentAt: voucher.send_callback_sent_at, rawPayload: parseJsonObject(voucher.raw_payload_json), createdAt: voucher.created_at, updatedAt: voucher.updated_at, } } function mapTaskReference(task: TaskRow) { return { taskId: task.id, taskNo: task.task_no, status: task.task_status, deliveryStatus: task.delivery_status, } } function normalizeAdminEtickets(value: unknown): JsonObject[] { if (!Array.isArray(value)) { return [] } const items: JsonObject[] = [] for (const item of value) { if (!item || typeof item !== 'object' || Array.isArray(item)) { continue } const current = item as JsonObject const id = String(current.id || '').trim() const code = String(current.code || '').trim() const num = Number(current.num || 1) if (!id && !code) { continue } items.push({ ...(id ? { id } : {}), ...(code ? { code } : {}), num: Number.isInteger(num) && num > 0 ? num : 1, }) } return items } function assertRequired(value: unknown, message: string) { if (value === undefined || value === null || String(value).trim() === '') { throw createHttpError(message, { statusCode: 400, errorCode: 'admin_kuaishou_industry_missing_required_field', }) } } function assertEtickets(value: unknown, message: string) { if (!Array.isArray(value) || value.length === 0) { throw createHttpError(message, { statusCode: 400, errorCode: 'admin_kuaishou_industry_missing_etickets', }) } } function parseJsonObject(value: unknown): JsonObject { if (value && typeof value === 'object' && !Array.isArray(value)) { return value as JsonObject } try { const parsed = JSON.parse(String(value || '{}')) return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} } catch { return {} } } function parseJsonArray(value: unknown): JsonObject[] { if (Array.isArray(value)) { return value.filter((item): item is JsonObject => Boolean(item && typeof item === 'object' && !Array.isArray(item)), ) } try { const parsed = JSON.parse(String(value || '[]')) return Array.isArray(parsed) ? parsed.filter((item): item is JsonObject => Boolean(item && typeof item === 'object' && !Array.isArray(item)), ) : [] } catch { return [] } }