Loading...
Loading...
Access Apple Card, Apple Cash, and Wallet financial data using FinanceKit. Use when querying transaction history, reading account balances, accessing Wallet orders, requesting financial data authorization, or building personal finance features that integrate with Apple's financial services.
npx skill4agent add dpearson2699/swift-ios-skills financekitcom.apple.developer.financekitNSFinancialDataUsageDescription<key>NSFinancialDataUsageDescription</key>
<string>This app uses your financial data to track spending and provide budgeting insights.</string>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
}guard FinanceStore.isDataAvailable(.orders) else { return }trueFinanceError.dataRestrictedlet 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().asset.liabilityiddisplayNameinstitutionNamecurrencyCodefunc 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.available.booked.availableAndBookedfunc 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)
}creditDebitIndicatorfunc 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"
}TransactionQuerylet 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// 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
])| Property | Type | Notes |
|---|---|---|
| | Unique per device |
| | Links to parent account |
| | When the transaction occurred |
| | When booked; nil if pending |
| | Always positive |
| | |
| | Display-friendly description |
| | Raw institution description |
| | Merchant name if available |
| | ISO 18245 code |
| | |
| | |
| | Foreign currency if applicable |
| | Exchange rate if applicable |
AsyncSequenceFinanceStore.ChangesHistoryTokenfunc monitorTransactions(for accountID: UUID) async throws {
let history = store.transactionHistory(
forAccountID: accountID,
since: loadSavedToken(),
isMonitoring: true // true = keep streaming; false = terminate after catch-up
)
for try await changes in history {
// changes.inserted, changes.updated, changes.deleted
saveToken(changes.newToken)
}
}HistoryTokenCodablefunc 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)
}FinanceError.historyTokenInvalidlet accountChanges = store.accountHistory(since: nil, isMonitoring: true)
let balanceChanges = store.accountBalanceHistory(forAccountID: accountID, since: nil, isMonitoring: true)TransactionPickerimport 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")
}
}
}
}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(
orderTypeIdentifier: "com.merchant.order",
orderIdentifier: "ORDER-123"
)
let result = try await store.containsOrder(matching: orderID, updatedDate: lastKnownDate)
// result: .exists, .newerExists, .olderExists, or .notFoundimport FinanceKitUI
AddOrderToWalletButton(signedArchive: orderData) { result in
// result: .success(SaveOrderResult) or .failure(Error)
}try await store.enableBackgroundDelivery(
for: [.transactions, .accountBalances],
frequency: .daily
).hourly.daily.weeklytry await store.disableBackgroundDelivery(for: [.transactions])
try await store.disableAllBackgroundDelivery()import FinanceKit
struct MyFinanceExtension: BackgroundDeliveryExtension {
var body: some BackgroundDeliveryExtensionProviding { FinanceDataHandler() }
}
struct FinanceDataHandler: BackgroundDeliveryExtensionProviding {
func didReceiveData(for dataTypes: [FinanceStore.BackgroundDataType]) async {
for dataType in dataTypes {
switch dataType {
case .transactions: await processNewTransactions()
case .accountBalances: await updateBalanceCache()
case .accounts: await refreshAccountList()
@unknown default: break
}
}
}
func willTerminate() async { /* Clean up */ }
}let store = FinanceStore.shared
let status = try await store.requestAuthorization() // Terminates if unavailableguard FinanceStore.isDataAvailable(.financialData) else {
showUnavailableMessage()
return
}
let status = try await FinanceStore.shared.requestAuthorization()let spent = transaction.transactionAmount.amount // Always positivelet amount = transaction.transactionAmount.amount
let signed = transaction.creditDebitIndicator == .debit ? -amount : amountlet transactions = try await store.transactions(query: query) // Fails if Wallet restrictedFinanceErrordo {
let transactions = try await store.transactions(query: query)
} catch let error as FinanceError {
if case .dataRestricted = error { showDataRestrictedMessage() }
}let allTransactions = try await store.transactions(query: TransactionQuery(
sortDescriptors: [SortDescriptor(\Transaction.transactionDate)],
predicate: nil, limit: nil, offset: nil
))let history = store.transactionHistory(
forAccountID: accountID,
since: loadSavedToken(),
isMonitoring: false
)
for try await changes in history {
processChanges(changes)
saveToken(changes.newToken)
}for try await changes in history {
processChanges(changes)
// Token lost -- next launch reprocesses everything
}for try await changes in history {
processChanges(changes)
saveToken(changes.newToken)
}.debit.creditFinanceStore.isDataAvailable(.financialData)com.apple.developer.financekitNSFinancialDataUsageDescription.authorized.denied.notDeterminedFinanceError.dataRestrictedCreditDebitIndicatorFinanceError.historyTokenInvalidisMonitoring: false