222 lines
7.2 KiB
TypeScript
222 lines
7.2 KiB
TypeScript
import { supplierDao } from '../dao/supplier.dao.js'
|
|
import { bridgeFetchWithAuth, findSupplierByIdentification, getBridgeApiBase, getBridgeToken, warmBridgeToken } from '../utils/bridge-auth.js'
|
|
import { sha256 } from '../utils/crypto.js'
|
|
import { logger } from '../utils/logger.js'
|
|
import { createSupplierOpenSignature, sm4CbcDecrypt } from '../utils/supplier-open-crypto.js'
|
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
|
|
|
type BridgePointsResponse = {
|
|
resultCode?: number
|
|
message?: string
|
|
userPoints?: number | string
|
|
}
|
|
|
|
export const supplierService = {
|
|
async decryptSupplierSession(keyword: string) {
|
|
if (!keyword) {
|
|
return serviceFail('用户参数不能为空')
|
|
}
|
|
|
|
if (/^[a-f0-9]{32}$/i.test(keyword)) {
|
|
const apiBase = getBridgeApiBase()
|
|
if (!apiBase) {
|
|
return serviceFail('平台积分商城接口地址未配置', 502, 'BRIDGE_SESSION_FAILED')
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${apiBase}/connect/supplier-session`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ keyword })
|
|
})
|
|
const payload = (await response.json()) as {
|
|
code?: number
|
|
msg?: string
|
|
data?: { userId?: unknown }
|
|
}
|
|
|
|
if (!response.ok || payload.code !== 0) {
|
|
return serviceFail(payload.msg || '获取用户会话失败')
|
|
}
|
|
|
|
const userId = String(payload.data?.userId || '').trim()
|
|
if (!userId) {
|
|
return serviceFail('用户参数无效')
|
|
}
|
|
|
|
return serviceOk({ userId })
|
|
} catch (error) {
|
|
logger.warn('supplier session exchange failed', {
|
|
message: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return serviceFail('获取用户会话失败', 502, 'BRIDGE_SESSION_FAILED')
|
|
}
|
|
}
|
|
|
|
const keyHex = process.env.SM4CBC_KEY_HEX || 'a1b2c3d4e5f60718293a4b5c6d7e8f90'
|
|
const ivHex = process.env.SM4CBC_IV_HEX || 'f9e8d7c6b5a49382716f0e1d2c3b4a5f'
|
|
const sessionKeyword = keyword.replace(/ /g, '+')
|
|
|
|
logger.info('supplier session decrypt input', {
|
|
keyword,
|
|
sessionKeyword,
|
|
hasKey: Boolean(keyHex),
|
|
hasIv: Boolean(ivHex),
|
|
keyLength: keyHex.length,
|
|
ivLength: ivHex.length
|
|
})
|
|
|
|
try {
|
|
const decryptedText = sm4CbcDecrypt(sessionKeyword, keyHex, ivHex)
|
|
logger.info('supplier session decrypt text', { decryptedText })
|
|
const payload = JSON.parse(decryptedText) as { userId?: unknown }
|
|
const userId = String(payload.userId || '').trim()
|
|
|
|
if (!userId) {
|
|
logger.warn('supplier session userId empty', { payload })
|
|
return serviceFail('用户参数无效')
|
|
}
|
|
|
|
return serviceOk({ userId })
|
|
} catch (error) {
|
|
logger.warn('supplier session decrypt failed', {
|
|
message: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return serviceFail('用户参数无效')
|
|
}
|
|
},
|
|
|
|
async getSupplierInfo(supplierIdentification: string) {
|
|
logger.info('supplier info query', { supplierIdentification })
|
|
|
|
if (!supplierIdentification) {
|
|
return serviceFail('供应商唯一标识不能为空')
|
|
}
|
|
|
|
const supplier = await supplierDao.findActiveByIdentification(supplierIdentification)
|
|
|
|
if (!supplier) {
|
|
return serviceFail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
}
|
|
|
|
return serviceOk({
|
|
id: supplier.id,
|
|
supplierIdentification: supplier.supplierIdentification,
|
|
supplierId: supplier.supplierId,
|
|
supplierName: supplier.supplierName
|
|
})
|
|
},
|
|
|
|
async warmSupplierToken(userId: string, supplierIdentification: string) {
|
|
logger.info('supplier token warmup', { userId, supplierIdentification })
|
|
|
|
if (!userId || !supplierIdentification) {
|
|
return serviceFail('用户信息和供应商唯一标识不能为空')
|
|
}
|
|
|
|
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
|
|
|
if (supplier instanceof Error) {
|
|
return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
}
|
|
|
|
try {
|
|
await warmBridgeToken(supplier)
|
|
} catch (error) {
|
|
return serviceFail(error instanceof Error ? error.message : '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED')
|
|
}
|
|
|
|
return serviceOk({
|
|
supplierIdentification,
|
|
supplierId: supplier.supplierId,
|
|
supplierName: supplier.supplierName,
|
|
userId
|
|
})
|
|
},
|
|
|
|
async getUserPoints(supplierId: string, userId: string) {
|
|
logger.info('supplier points query', { supplierId, userId })
|
|
|
|
if (!supplierId || !userId) {
|
|
return serviceFail('供应商或用户信息不能为空')
|
|
}
|
|
|
|
const { response, payload: pointsPayload } = await bridgeFetchWithAuth<BridgePointsResponse>(
|
|
supplierId,
|
|
'/pointOrder/queryPoints',
|
|
{ userId }
|
|
)
|
|
|
|
if (!response.ok || pointsPayload.resultCode !== 0) {
|
|
return serviceFail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED')
|
|
}
|
|
|
|
return serviceOk({
|
|
userPoints: Number(pointsPayload.userPoints || 0)
|
|
})
|
|
},
|
|
|
|
async getDebugTokens(supplierIdentification: string, forceRefreshZhghToken: boolean) {
|
|
logger.info('supplier debug tokens query', { supplierIdentification, forceRefreshZhghToken })
|
|
|
|
if (!supplierIdentification) {
|
|
return serviceFail('供应商唯一标识不能为空')
|
|
}
|
|
|
|
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
|
|
|
if (supplier instanceof Error) {
|
|
return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
}
|
|
|
|
const timestamp = Date.now()
|
|
const orderType = 2
|
|
const orderId = 'SUB_ORDER_ID_1,SUB_ORDER_ID_2'
|
|
const mainOrderId = 'MAIN_ORDER_ID'
|
|
const userId = 'USER_ID'
|
|
const supplierOpenToken = createSupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp)
|
|
const zhghBridgeToken = await getBridgeToken(supplier.supplierId, forceRefreshZhghToken)
|
|
|
|
return serviceOk({
|
|
supplierOpen: {
|
|
name: 'supplierOpenToken',
|
|
usage: '调用供应商开放接口 /supplierOpenApi/pointOrder/queryOrderInfo',
|
|
supplierId: supplier.supplierId,
|
|
clientId: supplier.clientId,
|
|
timestamp,
|
|
token: supplierOpenToken,
|
|
orderType,
|
|
orderId,
|
|
queryOrderInfoBody: {
|
|
supplierId: supplier.supplierId,
|
|
token: supplierOpenToken,
|
|
orderType,
|
|
orderId,
|
|
timestamp
|
|
}
|
|
},
|
|
zhghBridge: {
|
|
name: 'zhghBridgeToken',
|
|
usage: '调用 zhgh /open/pointOrder/* 桥接接口',
|
|
apiBase: getBridgeApiBase(),
|
|
supplierId: supplier.supplierId,
|
|
clientId: supplier.clientId,
|
|
timestamp,
|
|
sendSha256: sha256(`${supplier.clientId}${supplier.clientSecret}${timestamp}`),
|
|
token: zhghBridgeToken,
|
|
splitOrderBody: {
|
|
supplierId: supplier.supplierId,
|
|
token: zhghBridgeToken,
|
|
orderType,
|
|
orderId,
|
|
mainOrderId,
|
|
userId,
|
|
time: timestamp
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|