diff --git a/src/i18n/messages/wise.ts b/src/i18n/messages/wise.ts index ae7b569..dad1cf6 100644 --- a/src/i18n/messages/wise.ts +++ b/src/i18n/messages/wise.ts @@ -84,6 +84,7 @@ const wise = { no_sync_from: { "zh-TW": "未設切換日", en: "no cutover date" }, account_missing: { "zh-TW": "帳本帳戶不存在", en: "ledger account missing" }, currency_mismatch: { "zh-TW": "幣別不符", en: "currency mismatch" }, + sca_required: { "zh-TW": "Wise 要求強驗證(SCA),此帳戶無法以 API token 讀取對帳單", en: "Wise requires SCA for this statement; it can't be read with an API token" }, }, nothing: { "zh-TW": "沒有新的交易需要寫入。", en: "Nothing new to write." }, apply: { "zh-TW": "寫入 {count} 筆", en: "Write {count} entries" }, diff --git a/src/lib/mcp/tools-wise.ts b/src/lib/mcp/tools-wise.ts index 5de3df6..27af745 100644 --- a/src/lib/mcp/tools-wise.ts +++ b/src/lib/mcp/tools-wise.ts @@ -140,7 +140,7 @@ const SYNC_OUTPUT = { currency: { type: "string" }, reason: { type: "string", - enum: ["unmapped", "no_sync_from", "account_missing", "currency_mismatch"], + enum: ["unmapped", "no_sync_from", "account_missing", "currency_mismatch", "sca_required"], }, }, }, diff --git a/src/lib/wise-sync.ts b/src/lib/wise-sync.ts index 5c5e343..f55f2ae 100644 --- a/src/lib/wise-sync.ts +++ b/src/lib/wise-sync.ts @@ -6,6 +6,7 @@ import type { IntegrationConfig } from "@/lib/integrations/types"; import { discoverAccounts, withWiseClient, + WiseScaRequiredError, type WiseBalanceSummary, type WiseClient, type WiseProfileSummary, @@ -592,7 +593,7 @@ export type SkippedBalance = { profileName: string; balanceId: number; currency: string; - reason: "unmapped" | "no_sync_from" | "account_missing" | "currency_mismatch"; + reason: "unmapped" | "no_sync_from" | "account_missing" | "currency_mismatch" | "sca_required"; }; export type SyncResult = { @@ -980,9 +981,16 @@ export async function syncWiseTransactions(orgId: string, opts: SyncOptions): Pr skippedBalances.push(skippedEntry(m, profileName, check.reason)); continue; } - const { result, rows } = await planAccount(ctx, m, check.acct, check.syncFrom); - planned.push(...rows); - results.push(result); + // 個人 profile 的對帳單常被 Wise 要求 SCA(本系統不實作 SCA 簽章): + // 只跳過這個餘額並列在結果裡,不讓整次同步失敗、其他帳戶照常同步。 + try { + const { result, rows } = await planAccount(ctx, m, check.acct, check.syncFrom); + planned.push(...rows); + results.push(result); + } catch (e) { + if (!(e instanceof WiseScaRequiredError)) throw e; + skippedBalances.push(skippedEntry(m, profileName, "sca_required")); + } } const existing = await existingRefs(db, orgId, planned.map((p) => p.externalRef));