Financekit

作者 dpearson26998d90fd121a26無授權條款1.1K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫2 個月前更新

Access eligible Wallet financial data using FinanceKit and FinanceKitUI. Use when querying transactions or balances, reading Apple Card, Apple Cash, Savings, or U.K. connected-account data, requesting financial-data authorization, using TransactionPicker, enabling iOS 26 background delivery, or saving and checking Wallet orders.

AI 產生的概覽

指導開發者使用 Apple 的 FinanceKit 與 FinanceKitUI,在 Swift 應用程式中存取 Wallet 財務資料。

功能
此技能為建置 Apple 平台應用程式提供參考指引,協助透過 FinanceKit 與 FinanceKitUI 讀取符合條件的 Wallet 財務資料。內容涵蓋權限與 Info.plist 設定、資料可用性檢查、授權、帳戶與餘額查詢、交易查詢、以權杖為基礎的可續傳歷史記錄、TransactionPicker、Wallet 訂單,以及 iOS 26 背景遞送。它也列出常見錯誤與審查清單,並指向一份延伸模式參考文件。
適用情境
在實作或審查下列 Swift 程式碼時使用:查詢 Wallet 交易或餘額、讀取 Apple Card、Apple Cash、Savings 或英國連結帳戶資料、要求財務資料授權、使用 TransactionPicker、啟用 iOS 26 背景遞送,或儲存與檢查 Wallet 訂單。
執行需求
需要 Swift 6.3 與當前 Apple 平台、Xcode、經 Apple 核准的受管理 com.apple.developer.financekit 權限、具備 Account Holder 角色的組織層級 Apple Developer 帳號、符合條件的 App Store 應用程式,以及在 Info.plist 中設定 NSFinancialDataUsageDescription。不附帶指令碼;它是指令文件,另含一份參考文件與評估檔案。

FinanceKit

Access eligible financial data from Apple Wallet, including U.S. Apple Card, Apple Cash, Savings, and U.K. connected-account data. FinanceKit provides on-device access to accounts, balances, and transactions with user-controlled authorization. Targets Swift 6.3 / current Apple platforms; query APIs are available from iOS/iPadOS 17.4, TransactionPicker from iOS/iPadOS 18, and background delivery from iOS/iPadOS 26.

Keep FinanceKit guidance focused on financial-data access, Wallet order storage/querying, TransactionPicker, and background delivery. Route Apple Pay checkout to PassKit, widget UI/timeline work to WidgetKit, and Wallet order-tracking email or Apple Business Connect optimization outside this skill.

Contents

Setup and Entitlements

Requirements

  1. Managed entitlement -- request com.apple.developer.financekit from Apple via the FinanceKit entitlement request form. This is a managed capability; Apple reviews each application.
  2. Organization-level Apple Developer account (individual accounts are not eligible).
  3. Account Holder role required to request the entitlement.
  4. Eligible App Store app -- the app must be in the Finance category, distributed through the App Store for iPhone in the United States or United Kingdom, and provide financial-management tools such as net-worth, spending, or budgeting features.
  5. Per-bundle-ID approval -- Apple assigns the entitlement to the approved bundle ID; do not assume it applies to sibling apps or extensions automatically.
  6. If the app offers financial products directly or through a regulated institution, it must allow customers to connect those accounts to Apple Wallet and share the data with FinanceKit.

Project Configuration

  1. Add the FinanceKit entitlement through Xcode managed capabilities after Apple approves the request.
  2. Add NSFinancialDataUsageDescription to Info.plist -- this string is shown to the user during the authorization prompt.
  3. For iOS 26 background delivery, add the FinanceKit entitlement to both the app and extension targets, then use App Groups for shared storage.
xml
<key>NSFinancialDataUsageDescription</key><string>This app uses your financial data to track spending and provide budgeting insights.</string>

Data Availability

U.S. FinanceKit financial data requires iOS/iPadOS 17.4+ and currently covers eligible Apple Card, Apple Cash, and Savings data; Apple Card Family participants and Apple Cash Family children are excluded. U.K. support requires iOS/iPadOS 18.4+ and uses open banking for supported institutions. Orders APIs are available separately from financial-data query APIs.

Check whether the device supports FinanceKit before making any API calls. This value is constant across launches and iOS versions.

swift
import FinanceKit
guard FinanceStore.isDataAvailable(.financialData) else {    // FinanceKit not available -- do not call any other financial data APIs.    // The framework terminates the app if called when unavailable.    return}

For Wallet orders:

swift
guard FinanceStore.isDataAvailable(.orders) else { return }

Data availability returning true does not guarantee data exists on the device. Data access can also become temporarily restricted (e.g., Wallet unavailable, MDM restrictions). Restricted access throws FinanceError.dataRestricted rather than terminating.

Authorization

Request authorization to access user-selected financial accounts. The system presents an account picker where the user chooses which accounts to share and the earliest transaction date to expose.

swift
let store = FinanceStore.shared
let status = try await store.requestAuthorization()switch status {case .authorized:    break  // Proceed with queriescase .denied:        break  // User declinedcase .notDetermined: break  // No meaningful choice made@unknown default:    break}

Checking Current Status

Query current authorization without prompting:

swift
let currentStatus = try await store.authorizationStatus()

Once the user grants or denies access, requestAuthorization() returns the cached decision without showing the prompt again. Users can change access in Settings > Privacy & Security > Financial Data.

Querying Accounts

Accounts are modeled as an enum with two cases: .asset (e.g., Apple Cash, Savings) and .liability (e.g., Apple Card credit). Both share common properties (id, displayName, institutionName, currencyCode) while liability accounts add credit-specific fields.

swift
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)}

Working with Account Types

swift
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)")    }}

Account Balances

Balances represent the amount in an account at a point in time. A CurrentBalance is one of three cases: .available (includes pending), .booked (posted only), or .availableAndBooked.

swift
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)}

Reading Balance Amounts

Amounts are always positive decimals. Use creditDebitIndicator to determine the sign:

swift
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"}

Querying Transactions

Use TransactionQuery with Swift predicates, sort descriptors, limit, and offset.

swift
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)

Reading Transaction Data

swift
let amount = transaction.transactionAmountlet direction = transaction.creditDebitIndicator == .debit ? "spent" : "received"print("\(transaction.transactionDescription): \(direction) \(amount.amount) \(amount.currencyCode)")// merchantName, merchantCategoryCode, foreignCurrencyAmount are optional

Built-In Predicate Helpers

FinanceKit provides factory methods for common filters:

swift
// Filter by transaction statuslet bookedOnly = TransactionQuery.predicate(forStatuses: [.booked])
// Filter by transaction typelet purchases = TransactionQuery.predicate(forTransactionTypes: [.pointOfSale, .directDebit])
// Filter by merchant categorylet groceries = TransactionQuery.predicate(forMerchantCategoryCodes: [    MerchantCategoryCode(rawValue: 5411)  // Grocery stores])

For a transaction field table and more query patterns, read references/financekit-patterns.md [blocked].

Long-Running Queries and History

Use AsyncSequence-based history APIs for catch-up sync, live updates, or resumable sync. These return inserted, updated, and deleted item IDs plus a HistoryToken.

swift
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 Persistence

HistoryToken conforms to Codable. Persist it to resume queries without reprocessing data:

swift
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)}

If a saved token points to compacted history, the framework throws FinanceError.historyTokenInvalid. Discard the token, then immediately run a fresh catch-up query for the affected account or balance stream so local state and the replacement token are rebuilt. Use isMonitoring: true only for a separate live monitor.

Account and Balance History

swift
let accountChanges = store.accountHistory(since: nil, isMonitoring: true)let balanceChanges = store.accountBalanceHistory(forAccountID: accountID, since: nil, isMonitoring: true)

Ongoing budgeting sync should cover the data model the user authorized: account objects for account additions/removals, account balances for trend and widget state, and transactions for spending detail. Use separate history tokens per stream or account so a compacted token only forces resync of the affected stream.

Transaction Picker

For apps that need selective, ephemeral access without full authorization, use TransactionPicker from FinanceKitUI. Access is not persisted -- transactions are passed directly for immediate use.

swift
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 Orders

FinanceKit supports saving and querying Wallet orders (e.g., purchase receipts, shipping tracking).

Saving an Order

swift
let result = try await store.saveOrder(signedArchive: archiveData)switch result {case .added:        break  // Savedcase .cancelled:    break  // User cancelledcase .newerExisting: break // Newer version already in Wallet@unknown default:   break}

Checking for an Existing Order

swift
let orderID = FullyQualifiedOrderIdentifier(    orderTypeIdentifier: "com.merchant.order",    orderIdentifier: "ORDER-123")let result = try await store.containsOrder(matching: orderID, updatedDate: lastKnownDate)// result: .exists, .newerExists, .olderExists, or .notFound

Add Order to Wallet Button (FinanceKitUI)

swift
import FinanceKitUI
AddOrderToWalletButton(signedArchive: orderData) { result in    // result: .success(SaveOrderResult) or .failure(Error)}

Background Delivery

iOS 26+ supports background delivery extensions that notify your app of financial data changes outside its lifecycle. User authorization made in the main app is inherited by the extension. Both targets need the FinanceKit entitlement; use App Groups to share data between the app, extension, and related widgets.

Enabling Background Delivery

These registration methods are synchronous and nonthrowing; do not write try or await.

swift
store.enableBackgroundDelivery(    for: [.accounts, .accountBalances, .transactions],    frequency: .daily)

Available frequencies: .hourly, .daily, .weekly. These are expected minimum intervals between extension launches when data changes; longer frequencies give the extension a larger processing window.

Disable selectively or entirely:

swift
store.disableBackgroundDelivery(for: [.transactions])store.disableAllBackgroundDelivery()

Background Delivery Extension

Create a background delivery extension target in Xcode (Background Delivery Extension template). Implement the two async entry points directly on the extension type and return from didReceiveData(for:) only after essential work is saved.

swift
import FinanceKit
@mainstruct MyFinanceExtension: BackgroundDeliveryExtension {    func didReceiveData(for types: [FinanceStore.BackgroundDataType]) async {        if types.contains(.transactions) {            await processNewTransactions()        }        if types.contains(.accountBalances) {            await updateBalanceCache()        }        if types.contains(.accounts) {            await refreshAccountList()        }    }
    func willTerminate() async { await savePartialWork() }}

Common Mistakes

1. Calling APIs when data is unavailable

DON'T -- skip availability check:

swift
let store = FinanceStore.sharedlet status = try await store.requestAuthorization() // Terminates if unavailable

DO -- guard availability first:

swift
guard FinanceStore.isDataAvailable(.financialData) else {    showUnavailableMessage()    return}let status = try await FinanceStore.shared.requestAuthorization()

2. Ignoring the credit/debit indicator

DON'T -- treat amounts as signed values:

swift
let spent = transaction.transactionAmount.amount // Always positive

DO -- apply the indicator:

swift
let amount = transaction.transactionAmount.amountlet signed = transaction.creditDebitIndicator == .debit ? -amount : amount

3. Not handling data restriction errors

DON'T -- assume authorized access persists:

swift
let transactions = try await store.transactions(query: query) // Fails if Wallet restricted

DO -- catch FinanceError:

swift
do {    let transactions = try await store.transactions(query: query)} catch let error as FinanceError {    if case .dataRestricted = error { showDataRestrictedMessage() }}

4. Replacing resumable history with snapshots

Use the canonical transaction-history loop above and persist each newToken only after local deletes and upserts commit. Keep separate tokens per stream/account; on invalid-token errors, resync only the affected scope.

5. Misinterpreting credit/debit on liability accounts

Both asset and liability accounts use .debit for outgoing money. But .credit means different things: on an asset account it means money received; on a liability account it means a payment or refund that increases available credit. See references/financekit-patterns.md [blocked] for a full interpretation table.

Review Checklist

  • FinanceStore.isDataAvailable(.financialData) checked before any API call
  • App eligibility checked: Finance category, App Store iPhone distribution in the U.S. or U.K., financial-management feature set, organization account, Account Holder request
  • com.apple.developer.financekit entitlement requested and approved for the app bundle ID
  • NSFinancialDataUsageDescription set in Info.plist with a clear, specific message
  • Authorization status handled for all cases (.authorized, .denied, .notDetermined)
  • FinanceError.dataRestricted caught and handled gracefully
  • CreditDebitIndicator applied correctly to amounts (not treated as signed)
  • History tokens persisted for resumable queries
  • FinanceError.historyTokenInvalid handled by discarding token and immediately resyncing the affected stream
  • Ongoing sync plan covers authorized accounts, balances, and transactions, not transactions alone
  • Long-running queries use isMonitoring: false when live updates are not needed
  • Transaction picker used when full authorization is unnecessary
  • Only data the app genuinely needs is queried
  • Deleted IDs from history changes are explicitly removed from local account, balance, or transaction storage
  • Background delivery calls use the synchronous iOS 26 APIs and the extension is in the same App Group as the main app
  • Background delivery registers every needed data type: .accounts, .accountBalances, and/or .transactions
  • FinanceKit entitlement added to both app and background delivery extension targets
  • Financial data deleted when user revokes access

References

來源與署名

來源:dpearson2699/swift-ios-skills位於skills/financekit提交8d90fd1

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架

更多來自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指導實作、審查與改進 iOS、iPadOS、watchOS 與 CarPlay 上的 WidgetKit 小工具與控制項。

Software Development1.1K2 個月前更新

Weatherkit

dpearson2699

指導 iOS 開發者使用 WeatherService 取得 WeatherKit 預報、警報與署名資訊。

Software Development1.1K2 個月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分類1.1K2 個月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分類1.1K2 個月前更新

Tabletopkit

dpearson2699

指導使用 TabletopKit 在 visionOS 上打造多人空間桌遊,涵蓋棋具、座位、動作與 RealityKit 算繪。

Software Development1.1K2 個月前更新

Swiftui Webkit

dpearson2699

指導在 iOS 26 及更新版本的 SwiftUI App 中使用 WebKit for SwiftUI 嵌入與控制網頁內容。

Software Development1.1K2 個月前更新