使用 FinanceKit 和 FinanceKitUI 获取符合条件的 Wallet 财务数据。适用场景包括:查询交易明细或余额,读取 Apple Card、Apple Cash、Savings 存款账户及英国关联账户数据,请求财务数据授权,使用 TransactionPicker,开启 iOS 26 后台推送,以及保存和检查 Wallet 订单。
FinanceKit
获取来自 Apple Wallet 的符合条件的财务数据,包括美国地区的 Apple Card、Apple Cash、Savings 存款账户以及英国地区的关联账户数据。FinanceKit 提供由用户完全控制授权的设备端账户、余额与交易记录访问能力。目标平台为 Swift 6.3 及当前 Apple 平台;查询 API 需 iOS/iPadOS 17.4+,TransactionPicker 需 iOS/iPadOS 18+,后台推送(Background Delivery)需 iOS/iPadOS 26+。
请保持 FinanceKit 相关指导聚焦于财务数据访问、Wallet 订单存储/查询、TransactionPicker 和后台推送。若涉及 Apple Pay 结账请引导至 PassKit,小组件 UI/时间线开发请引导至 WidgetKit,而 Wallet 订单跟踪邮件或 Apple Business Connect 优化则不属于本 Skill 的范畴。
目录
- 配置与 Entitlement 权限
- 数据可用性
- 用户授权
- 查询账户
- 账户余额
- 查询交易记录
- 长轮询查询与历史记录
- 交易选择器 (Transaction Picker)
- Wallet 订单
- 后台推送 (Background Delivery)
- 常见错误
- 审查清单
- 参考资料
配置与 Entitlement 权限
申请前提
- 托管 Entitlement (Managed entitlement) —— 通过 FinanceKit Entitlement 申请表单 向 Apple 申请
com.apple.developer.financekit。这是一项托管权限,Apple 会对每个应用进行严格审核。 - 组织级 Apple Developer 账号(个人开发者账号不具备申请资格)。
- 需具备 Account Holder (账号持有人) 角色方可提交申请。
- 符合条件的 App Store 应用 —— 应用必须归属于财务(Finance)分类,通过美国或英国的 iPhone App Store 分发,并提供资产净值、消费追踪或预算管理等财务管理工具。
- 按 Bundle ID 独立审核 —— Apple 会将权限绑定到已批准的 Bundle ID,切勿假设其会自动适用于同系列兄弟应用或扩展。
- 如果应用本身或通过受监管机构直接提供金融产品,则必须允许客户将这些账户连接到 Apple Wallet 并与 FinanceKit 共享数据。
项目配置
- Apple 批准申请后,通过 Xcode 的 Managed Capabilities 添加 FinanceKit Entitlement。
- 在 Info.plist 中添加
NSFinancialDataUsageDescription键 —— 该字符串将在弹出授权提示框时展示给用户。 - 对于 iOS 26 的后台推送(Background Delivery),需同时为 App Target 和 Extension Target 添加 FinanceKit Entitlement,并使用 App Groups 进行共享存储。
<key>NSFinancialDataUsageDescription</key>
<string>此应用需要使用您的财务数据来追踪支出并提供预算分析。</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。该返回值在应用启动和 iOS 版本之间保持恒定。
import FinanceKit
guard FinanceStore.isDataAvailable(.financialData) else {
// FinanceKit 不可用 -- 切勿调用任何其他财务数据 API。
// 如果在不可用状态下调用,框架将直接终止应用。
return
}
对于 Wallet 订单:
guard FinanceStore.isDataAvailable(.orders) else { return }
数据可用性返回 true 并不保证设备上一定存在数据。数据访问也可能临时受限(例如 Wallet 不可用、MDM 限制)。受限访问会抛出 FinanceError.dataRestricted 而不是终止应用。
用户授权
请求访问用户选定的财务账户授权。系统会展示一个账户选择器,由用户选择要共享哪些账户以及暴露的最早交易日期。
let store = FinanceStore.shared
let status = try await store.requestAuthorization()
switch status {
case .authorized: break // 继续执行查询
case .denied: break // 用户拒绝
case .notDetermined: break // 未做出明确选择
@unknown default: break
}
检查当前授权状态
在不弹窗提示的情况下查询当前授权状态:
let currentStatus = try await store.authorizationStatus()
一旦用户授予或拒绝访问,requestAuthorization() 将直接返回缓存的决定,不再重复展示弹窗。用户随时可以在“设置 > 隐私与安全性 > 财务数据”中修改访问权限。
查询账户
账户在 Swift 中被建模为一个枚举,包含两个 case:.asset(资产账户,如 Apple Cash、Savings)和 .liability(负债账户,如 Apple Card 信用卡)。两者共享通用属性(id、displayName、institutionName、currencyCode),而负债账户额外包含信用卡特有的字段。
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.currencyCode)")
case .liability(let liability):
if let limit = liability.creditInformation.creditLimit {
print("信用额度:\(limit.amount) \(limit.currencyCode)")
}
}
账户余额
余额代表账户在特定时间点的金额。CurrentBalance 分为三种情况:.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)"
}
// 从 CurrentBalance 枚举中提取:
switch balance.currentBalance {
case .available(let bal): formatBalance(bal)
case .booked(let bal): formatBalance(bal)
case .availableAndBooked(let available, _): formatBalance(available)
@unknown default: "未知"
}
查询交易记录
结合 Swift 谓词(predicates)、排序描述符(sort descriptors)、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 ? "支出" : "收入"
print("\(transaction.transactionDescription): \(direction) \(amount.amount) \(amount.currencyCode)")
// merchantName、merchantCategoryCode、foreignCurrencyAmount 为可选属性
内置谓词辅助函数
FinanceKit 为常用筛选条件提供了工厂方法:
// 按交易状态筛选
let bookedOnly = TransactionQuery.predicate(forStatuses: [.booked])
// 按交易类型筛选
let purchases = TransactionQuery.predicate(forTransactionTypes: [.pointOfSale, .directDebit])
// 按商户类别筛选
let groceries = TransactionQuery.predicate(forMerchantCategoryCodes: [
MerchantCategoryCode(rawValue: 5411) // 杂货店/超市
])
有关交易字段对照表和更多查询模式,请参阅 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 // 追赶完已保存 token 的历史变更后即结束
)
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)的历史记录,框架将抛出 FinanceError.historyTokenInvalid。此时应丢弃该 token,并立即为受影响的账户或余额流重新发起一次全量追赶查询,以重建本地状态和新的 token。仅在单独的实时监听任务中使用 isMonitoring: true。
账户与余额历史记录
let accountChanges = store.accountHistory(since: nil, isMonitoring: true)
let balanceChanges = store.accountBalanceHistory(forAccountID: accountID, since: nil, isMonitoring: true)
持续的预算同步应覆盖用户授权的数据模型:账户对象(处理账户添加/移除)、账户余额(处理趋势和 Widget 状态)以及交易记录(处理消费明细)。建议为每个数据流或账户使用独立的 history token,这样当某个 token 失效压缩时,仅需重新同步受影响的单个数据流。
交易选择器 (Transaction Picker)
对于只需要选择性、临时访问而不需要完整授权的应用,可以使用 FinanceKitUI 中的 TransactionPicker。访问权限不会被持久化 —— 选中的交易记录将直接传递给应用供即时使用。
import FinanceKitUI
struct ExpenseImportView: View {
@State private var selectedTransactions: [Transaction] = []
var body: some View {
if FinanceStore.isDataAvailable(.financialData) {
TransactionPicker(selection: $selectedTransactions) {
Label("导入交易记录", systemImage: "creditcard")
}
}
}
}
Wallet 订单
FinanceKit 支持保存和查询 Wallet 订单(如购买收据、物流跟踪信息)。
保存订单
let result = try await store.saveOrder(signedArchive: archiveData)
switch result {
case .added: break // 保存成功
case .cancelled: break // 用户取消
case .newerExisting: break // Wallet 中已存在更新的版本
@unknown default: break
}
检查是否存在已有订单
let orderID = FullyQualifiedOrderIdentifier(
orderTy
<!-- truncated for translation batch; full body continues in source -->






