/**
* FX-CRM API 客戶端(共用)
*/
import { getAppSecrets } from '../../utils/secrets.js'
import { logger } from '../../middleware/logging.js'
/** FX-CRM Access Token 快取 */
let accessTokenCache: { token: string; expiresAt: number } | null = null
const FXCRM_BASE_URL = 'https://open.fxiaoke.com'
/**
* 取得 FX-CRM Corp Access Token
*/
export async function getCorpAccessToken(): Promise<string> {
// 檢查快取
if (accessTokenCache && accessTokenCache.expiresAt > Date.now()) {
return accessTokenCache.token
}
const secrets = await getAppSecrets()
const response = await fetch(
`${FXCRM_BASE_URL}/cgi/corpAccessToken/get/V2`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appId: secrets.fxcrmAppId,
appSecret: secrets.fxcrmAppSecret,
permanentCode: secrets.fxcrmPermanentCode,
}),
}
)
const data = await response.json() as any
if (data.errorCode !== 0) {
throw new Error(`FX-CRM 取得 Access Token 失敗: ${data.errorMessage}`)
}
// 快取 token(提前 5 分鐘過期)
accessTokenCache = {
token: data.corpAccessToken,
expiresAt: Date.now() + (data.expiresIn - 300) * 1000,
}
return data.corpAccessToken
}
/**
* 查詢 FX-CRM 資料
*/
export async function queryFxcrm(
objectApiName: string,
filters: Array<{ field_name: string; field_values: any[]; operator: string }>,
options?: {
fieldProjection?: string[]
limit?: number
offset?: number
}
): Promise<any[]> {
const secrets = await getAppSecrets()
const accessToken = await getCorpAccessToken()
// 構建查詢資料
const searchQueryInfo: Record<string, any> = {
limit: options?.limit ?? 100,
offset: options?.offset ?? 0,
}
// 只有當 filters 有內容時才傳入
if (filters && filters.length > 0) {
searchQueryInfo.filters = filters
}
if (options?.fieldProjection) {
searchQueryInfo.fieldProjection = options.fieldProjection
}
// 使用 custom 端點,支援自定義物件(如 object_bfeui__c, work_item__c 等)
const response = await fetch(
`${FXCRM_BASE_URL}/cgi/crm/custom/v2/data/query`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
corpAccessToken: accessToken,
corpId: secrets.fxcrmCorpId,
currentOpenUserId: secrets.fxcrmDefaultUserId,
data: {
dataObjectApiName: objectApiName,
search_query_info: searchQueryInfo,
},
}),
}
)
const data = await response.json() as any
logger.info({
objectApiName,
errorCode: data.errorCode,
errorMessage: data.errorMessage,
dataCount: data.data?.dataList?.length,
}, 'FX-CRM 查詢回應')
if (data.errorCode !== 0) {
logger.error({
errorCode: data.errorCode,
errorMessage: data.errorMessage,
objectApiName,
filters: filters?.length,
}, 'FX-CRM 查詢失敗')
throw new Error(`FX-CRM 查詢失敗 (${data.errorCode}): ${data.errorMessage}`)
}
return data.data?.dataList || []
}
/**
* 清除 Access Token 快取
*/
export function clearAccessTokenCache(): void {
accessTokenCache = null
}