使用 FinanceKit 與 FinanceKitUI 存取符合資格的 Apple Wallet 財務資料。適用於查詢交易紀錄或帳戶餘額、讀取 Apple Card、Apple Cash、Savings 或英國連結帳戶資料、請求財務資料授權、使用 TransactionPicker、啟用 iOS 26 背景傳送,或是儲存與檢查 Wallet 訂單。
FinanceKit
存取來自 Apple Wallet 的符合資格財務資料,包含美國 Apple Card、Apple Cash、Savings 以及英國連結帳戶資料。FinanceKit 提供裝置端存取帳戶、餘額與交易紀錄的功能,並由使用者掌控授權。本 Skill 針對 Swift 6.3 / 目前 Apple 平台;查詢 API 自 iOS/iPadOS 17.4 起提供,TransactionPicker 自 iOS/iPadOS 18 起提供,背景傳送自 iOS/iPadOS 26 起提供。
請將 FinanceKit 的指引集中在財務資料存取、Wallet 訂單儲存/查詢、TransactionPicker 以及背景傳送。Apple Pay 結帳請導向 PassKit,Widget UI/timeline 相關工作導向 WidgetKit,而 Wallet 訂單追蹤 Email 或 Apple Business Connect 的最佳化則不在本 Skill 的範圍內。
目錄
- 設定與 Entitlements
- 資料可用性
- 授權
- 查詢帳戶
- 帳戶餘額
- 查詢交易紀錄
- 長時間執行的查詢與歷史紀錄
- Transaction Picker
- Wallet 訂單
- 背景傳送
- 常見錯誤
- 審查檢核表
- 參考資料
設定與 Entitlements
需求條件
- Managed entitlement -- 透過 FinanceKit entitlement 申請表單 向 Apple 申請
com.apple.developer.financekit。這是一項受管理的權限(managed capability),Apple 會審查每個應用程式。 - 組織級 Apple Developer 帳號(個人帳號不符合資格)。
- 需要 Account Holder(帳號持有者)角色 才能申請此 entitlement。
- 符合資格的 App Store App -- App 必須屬於財務(Finance)分類,透過美國或英國的 App Store 發行於 iPhone,並提供財務管理工具,例如淨資產、消費或預算規劃功能。
- 按 Bundle ID 獨立核准 -- Apple 會將 entitlement 分配給獲得核准的 Bundle ID;切勿假設它會自動套用到同系列的其它 App 或 Extension。
- 若 App 直接或透過受管制的機構提供財務產品,則必須允許顧客將這些帳戶連結至 Apple Wallet 並將資料分享給 FinanceKit。
專案設定
- 在 Apple 核准申請後,透過 Xcode 的 Managed Capabilities 新增 FinanceKit entitlement。
- 在 Info.plist 中新增
NSFinancialDataUsageDescription-- 系統會在授權提示視窗中向使用者顯示此字串。 - 針對 iOS 26 的背景傳送,需在 App Target 與 Extension Target 同時新增 FinanceKit entitlement,並使用 App Groups 來設定共享儲存空間。
<key>NSFinancialDataUsageDescription</key>
<string>This app uses your financial data to track spending and provide budgeting insights.</string>
資料可用性
美國地區的 FinanceKit 財務資料需要 iOS/iPadOS 17.4+,目前涵蓋符合資格的 Apple Card、Apple Cash 與 Savings 資料;Apple Card Family 共享成員與 Apple Cash Family 兒童帳戶不在支援範圍內。英國地區支援需要 iOS/iPadOS 18.4+,並對支援的金融機構採用開放銀行(Open Banking)標準。Orders API 係獨立於財務資料查詢 API 之外提供。
在呼叫任何 API 之前,請先檢查裝置是否支援 FinanceKit。此數值在 App 每次啟動及各 iOS 版本之間皆保持恆定。
import FinanceKit
guard FinanceStore.isDataAvailable(.financialData) else {
// FinanceKit 無法使用 -- 請勿呼叫任何其他財務資料 API。
// 若在不可用時呼叫,Framework 會直接終止 App 執行。
return
}
針對 Wallet 訂單:
guard FinanceStore.isDataAvailable(.orders) else { return }
資料可用性傳回 true 並不保證裝置上一定存在資料。資料存取權限也可能暫時受限(例如 Wallet 無法使用、MDM 限制等)。存取受限時會拋出 FinanceError.dataRestricted,而非直接終止 App。
授權
請求存取使用者所選財務帳戶的授權。系統會顯示帳戶選擇器,供使用者選擇要分享哪些帳戶以及開放的最早交易日期。
let store = FinanceStore.shared
let status = try await store.requestAuthorization()
switch status {
case .authorized: break // Proceed with queries
case .denied: break // User declined
case .notDetermined: break // No meaningful choice made
@unknown default: break
}
檢查當前狀態
在不跳出提示視窗的情況下查詢當前授權狀態:
let currentStatus = try await store.authorizationStatus()
使用者一旦授予或拒絕存取權限,requestAuthorization() 就會傳回快取的決定,不再重新顯示提示視窗。使用者可至「設定」>「隱私權與安全性」>「財務資料」中修改權限。
查詢帳戶
帳戶模型為包含兩種 case 的 enum:.asset(例如 Apple Cash、Savings)與 .liability(例如 Apple Card 信用卡)。兩者皆共享通用屬性(id、displayName、institutionName、currencyCode),而負債帳戶(liability)則另外增加信用卡專屬欄位。
func fetchAccounts() async throws -> [Account] {
let query = AccountQuery(
sortDescriptors: [SortDescriptor(\Account.displayName)],
predicate: nil,
limit: nil,
offset: nil
)
return try await store.accounts(query: query)
}
處理帳戶類型
switch account {
case .asset(let asset):
print("Asset account, currency: \(asset.currencyCode)")
case .liability(let liability):
if let limit = liability.creditInformation.creditLimit {
print("Credit limit: \(limit.amount) \(limit.currencyCode)")
}
}
帳戶餘額
餘額代表特定時間點帳戶內的金額。CurrentBalance 為以下三種 case 之一:.available(包含待處理)、.booked(僅限已過帳)或 .availableAndBooked。
func fetchBalances(for accountID: UUID) async throws -> [AccountBalance] {
let predicate = #Predicate<AccountBalance> { balance in
balance.accountID == accountID
}
let query = AccountBalanceQuery(
sortDescriptors: [SortDescriptor(\AccountBalance.id)],
predicate: predicate,
limit: nil,
offset: nil
)
return try await store.accountBalances(query: query)
}
讀取餘額金額
金額恆為正數的 Decimal。請使用 creditDebitIndicator 來判斷正負號:
func formatBalance(_ balance: Balance) -> String {
let sign = balance.creditDebitIndicator == .debit ? "-" : ""
return "\(sign)\(balance.amount.amount) \(balance.amount.currencyCode)"
}
// Extract from CurrentBalance enum:
switch balance.currentBalance {
case .available(let bal): formatBalance(bal)
case .booked(let bal): formatBalance(bal)
case .availableAndBooked(let available, _): formatBalance(available)
@unknown default: "Unknown"
}
查詢交易紀錄
搭配 Swift predicate、排序描述子(sort descriptor)、limit 以及 offset 使用 TransactionQuery。
let predicate = #Predicate<Transaction> { $0.accountID == accountID }
let query = TransactionQuery(
sortDescriptors: [SortDescriptor(\Transaction.transactionDate, order: .reverse)],
predicate: predicate,
limit: 50,
offset: nil
)
let transactions = try await store.transactions(query: query)
讀取交易資料
let amount = transaction.transactionAmount
let direction = transaction.creditDebitIndicator == .debit ? "spent" : "received"
print("\(transaction.transactionDescription): \(direction) \(amount.amount) \(amount.currencyCode)")
// merchantName, merchantCategoryCode, foreignCurrencyAmount are optional
內建 Predicate 輔助方法
FinanceKit 針對常用篩選條件提供了工廠方法(factory methods):
// Filter by transaction status
let bookedOnly = TransactionQuery.predicate(forStatuses: [.booked])
// Filter by transaction type
let purchases = TransactionQuery.predicate(forTransactionTypes: [.pointOfSale, .directDebit])
// Filter by merchant category
let groceries = TransactionQuery.predicate(forMerchantCategoryCodes: [
MerchantCategoryCode(rawValue: 5411) // Grocery stores
])
想了解交易欄位表與更多查詢模式,請參閱 references/financekit-patterns.md。
長時間執行的查詢與歷史紀錄
使用基於 AsyncSequence 的歷史紀錄 API 來進行追趕同步(catch-up sync)、即時更新或可中斷重連的同步。這些 API 會傳回新增、更新與刪除的項目 ID 以及一個 HistoryToken。
func catchUpTransactions(for accountID: UUID) async throws {
let history = store.transactionHistory(
forAccountID: accountID,
since: loadSavedToken(),
isMonitoring: false // finish after saved-token catch-up
)
for try await changes in history {
removeLocalRecords(withIDs: changes.deleted)
upsert(changes.inserted + changes.updated)
saveToken(changes.newToken)
}
}
History Token 的持久化儲存
HistoryToken 遵循 Codable 協定。可將其持久化儲存以在不重新處理資料的情況下恢復查詢:
func saveToken(_ token: FinanceStore.HistoryToken) {
if let data = try? JSONEncoder().encode(token) {
UserDefaults.standard.set(data, forKey: "financeHistoryToken")
}
}
func loadSavedToken() -> FinanceStore.HistoryToken? {
guard let data = UserDefaults.standard.data(forKey: "financeHistoryToken") else { return nil }
return try? JSONDecoder().decode(FinanceStore.HistoryToken.self, from: data)
}
如果儲存的 token 指向已精簡/壓縮(compacted)的歷史紀錄,Framework 會拋出 FinanceError.historyTokenInvalid。此時應捨棄該 token,並立即針對受影響的帳戶或餘額串流(balance stream)執行一次全新的追趕查詢,重新建立本地狀態與替代 token。僅在獨立的即時監控流程中才使用 isMonitoring: true。
帳戶與餘額歷史紀錄
let accountChanges = store.accountHistory(since: nil, isMonitoring: true)
let balanceChanges = store.accountBalanceHistory(forAccountID: accountID, since: nil, isMonitoring: true)
持續進行的預算同步應涵蓋使用者已授權的資料模型:用於帳戶新增/移除的帳戶物件(account objects)、用於趨勢與 Widget 狀態的帳戶餘額(account balances),以及用於消費明細的交易紀錄(transactions)。請為每個串流或帳戶使用獨立的 history token,如此一來,精簡失效的 token 只會強制重同步受影響的串流。
Transaction Picker
對於需要選擇性、一次性(ephemeral)存取而無需完整授權的 App,請使用 FinanceKitUI 提供的 TransactionPicker。此存取權限不會被持久化儲存 -- 交易資料會直接傳遞供立即使用。
import FinanceKitUI
struct ExpenseImportView: View {
@State private var selectedTransactions: [Transaction] = []
var body: some View {
if FinanceStore.isDataAvailable(.financialData) {
TransactionPicker(selection: $selectedTransactions) {
Label("Import Transactions", systemImage: "creditcard")
}
}
}
}
Wallet 訂單
FinanceKit 支援儲存與查詢 Wallet 訂單(例如購買收據、物流追蹤)。
儲存訂單
let result = try await store.saveOrder(signedArchive: archiveData)
switch result {
case .added: break // Saved
case .cancelled: break // User cancelled
case .newerExisting: break // Newer version already in Wallet
@unknown default: break
}
檢查既有訂單
let orderID = FullyQualifiedOrderIdentifier(
orderTy
<!-- truncated for translation batch; full body continues in source -->






