financekit

financekit

熱門

使用 FinanceKit 與 FinanceKitUI 存取符合資格的 Apple Wallet 財務資料。適用於查詢交易紀錄或帳戶餘額、讀取 Apple Card、Apple Cash、Savings 或英國連結帳戶資料、請求財務資料授權、使用 TransactionPicker、啟用 iOS 26 背景傳送,或是儲存與檢查 Wallet 訂單。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
financekit
描述

使用 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

需求條件

  1. Managed entitlement -- 透過 FinanceKit entitlement 申請表單 向 Apple 申請 com.apple.developer.financekit。這是一項受管理的權限(managed capability),Apple 會審查每個應用程式。
  2. 組織級 Apple Developer 帳號(個人帳號不符合資格)。
  3. 需要 Account Holder(帳號持有者)角色 才能申請此 entitlement。
  4. 符合資格的 App Store App -- App 必須屬於財務(Finance)分類,透過美國或英國的 App Store 發行於 iPhone,並提供財務管理工具,例如淨資產、消費或預算規劃功能。
  5. 按 Bundle ID 獨立核准 -- Apple 會將 entitlement 分配給獲得核准的 Bundle ID;切勿假設它會自動套用到同系列的其它 App 或 Extension。
  6. 若 App 直接或透過受管制的機構提供財務產品,則必須允許顧客將這些帳戶連結至 Apple Wallet 並將資料分享給 FinanceKit。

專案設定

  1. 在 Apple 核准申請後,透過 Xcode 的 Managed Capabilities 新增 FinanceKit entitlement。
  2. 在 Info.plist 中新增 NSFinancialDataUsageDescription -- 系統會在授權提示視窗中向使用者顯示此字串。
  3. 針對 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 信用卡)。兩者皆共享通用屬性(iddisplayNameinstitutionNamecurrencyCode),而負債帳戶(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 -->