mcpField
F
financekit

financekit

github.com/dpearson2699データ・データベース1.2k3.1k 回インストール13 週間前に更新
リポジトリ

financekit について

Appleプラットフォームのアプリ開発者向けに、ユーザーの許可を得てApple Wallet内の対象口座、残高、取引データへアクセスするFinanceKitのガイドです。Walletの注文、取引選択、バックグラウンド更新とアクセス要件も扱います。

主な機能

  1. 01金融データへのアクセス許可を要求
  2. 02口座、残高、取引の照会
  3. 03履歴APIによる変更の同期
  4. 04TransactionPickerで取引を選択
  5. 05Walletの注文を保存・照会
  6. 06データのバックグラウンド更新を登録

活用シーン

  1. 01家計管理アプリで口座残高を集計
  2. 02取引を同期して支出記録を更新
  3. 03ユーザーが取引を選んで一度だけ取り込み
  4. 04注文をApple Walletに保存

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill financekit

インストール後、Claude Code で/financekitを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/financekit-patterns.md

詳細

FinanceKit

Apple Wallet の対象となる金融データにアクセスできます。対象には、米国の Apple Card、Apple Cash、Savings、および英国の接続済み口座データが含まれます。FinanceKit は、ユーザーが管理する認証に基づき、デバイス上で口座、残高、取引にアクセスできます。Swift 6.3/最新の Apple プラットフォームを対象とします。クエリ API は iOS/iPadOS 17.4 以降、TransactionPicker は iOS/iPadOS 18 以降、バックグラウンド配信は iOS/iPadOS 26 以降で利用できます。

FinanceKit に関する説明は、金融データへのアクセス、Wallet の注文の保存とクエリ、TransactionPicker、バックグラウンド配信に絞ってください。Apple Pay の決済は PassKit に、ウィジェットの UI/タイムライン関連の作業は WidgetKit に案内してください。Wallet の注文追跡メールや Apple Business Connect の最適化は、このスキルの対象外です。

目次

セットアップとエンタイトルメント

要件
  1. 管理対象エンタイトルメント -- FinanceKit エンタイトルメント申請フォームから Apple に com.apple.developer.financekit を申請します。これは管理対象の機能であり、Apple が各アプリケーションを審査します。
  2. 組織レベルの Apple Developer アカウント(個人アカウントは対象外です)。
  3. Account Holder の役割がエンタイトルメントの申請に必要です。
  4. 対象となる App Store アプリ -- アプリは Finance カテゴリに属し、米国または英国の App Store で iPhone 向けに配信され、純資産、支出、予算管理などの金融管理ツールを提供する必要があります。
  5. バンドル ID ごとの承認 -- Apple は承認されたバンドル ID にエンタイトルメントを付与します。関連アプリや拡張機能にも自動的に適用されるとは限りません。
  6. アプリが金融商品を直接、または規制対象機関を通じて提供する場合、顧客がそれらの口座を Apple Wallet に接続し、FinanceKit とデータを共有できるようにする必要があります。
プロジェクトの設定
  1. Apple に承認された後、Xcode の管理対象機能から FinanceKit エンタイトルメントを追加します。
  2. Info.plist に NSFinancialDataUsageDescription を追加します。この文字列は認証プロンプトでユーザーに表示されます。
  3. iOS 26 のバックグラウンド配信を利用するには、アプリと拡張機能の両方のターゲットに FinanceKit エンタイトルメントを追加し、共有ストレージには 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 以降が必要で、対応する金融機関ではオープンバンキングを使用します。注文 API は金融データのクエリ API とは別に利用できます。

API を呼び出す前に、デバイスが FinanceKit に対応しているか確認してください。この値は、起動や iOS のバージョンにかかわらず一定です。

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
}

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  // 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(Apple Cash、Savings など)と .liability(Apple Card のクレジットなど)の 2 つのケースを持つ列挙型としてモデル化されています。どちらも共通のプロパティ(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 account, currency: \(asset.currencyCode)")
case .liability(let liability):
    if let limit = liability.creditInformation.creditLimit {
        print("Credit limit: \(limit.amount) \(limit.currencyCode)")
    }
}

アカウント残高

残高は、ある時点におけるアカウント内の金額を表します。CurrentBalance には、.available(保留中の取引を含む)、.booked(計上済みのみ)、.availableAndBooked の 3 つのケースがあります。

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)
}
残高の金額を読み取る

金額は常に正の小数です。符号を判定するには 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 の述語、ソート記述子、件数制限、オフセットを指定した 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
組み込みの述語ヘルパー

FinanceKit には、一般的なフィルター用のファクトリーメソッドが用意されています。

// 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 を使用します。これらの 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)
    }
}
履歴トークンの永続化

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

保存したトークンが圧縮済みの履歴を指している場合、フレームワークは FinanceError.historyTokenInvalid をスローします。トークンを破棄し、対象アカウントまたは残高ストリームに対して直ちに新しいキャッチアップクエリを実行して、ローカル状態と置き換え用トークンを再構築してください。isMonitoring: true を使用するのは、別のライブモニターを設定する場合に限ります。

アカウントと残高の履歴
let accountChanges = store.accountHistory(since: nil, isMonitoring: true)
let balanceChanges = store.accountBalanceHistory(forAccountID: accountID, since: nil, isMonitoring: true)

継続的な予算管理の同期では、ユーザーが認可したデータモデルを対象にする必要があります。アカウントの追加や削除にはアカウントオブジェクト、推移やウィジェットの状態にはアカウント残高、支出の詳細には取引を使用します。圧縮されたトークンによる再同期が影響を受けたストリームのみに限られるよう、ストリームまたはアカウントごとに個別の履歴トークンを使用してください。

取引ピッカー

完全な認可を必要とせず、選択的かつ一時的なアクセスが必要なアプリでは、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(
    orderTypeIdentifier: "com.merchant.order",
    orderIdentifier: "ORDER-123"
)
let result = try await store.containsOrder(matching: orderID, updatedDate: lastKnownDate)
// result: .exists, .newerExists, .olderExists, or .notFound
Wallet に追加ボタン(FinanceKitUI)
import FinanceKitUI

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

バックグラウンド配信

iOS 26 以降では、アプリのライフサイクル外で金融データの変更をアプリに通知するバックグラウンド配信エクステンションに対応しています。メインアプリで行ったユーザー認可はエクステンションにも引き継がれます。両方のターゲットに FinanceKit のエンタイトルメントが必要です。アプリ、エクステンション、関連ウィジェット間でデータを共有するには App Groups を使用します。

バックグラウンド配信を有効にする

これらの登録メソッドは同期処理で、エラーをスローしません。try や await は記述しないでください。

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

利用できる頻度は .hourly、.daily、.weekly です。データが変更された際にエクステンションが起動するまでの最小間隔の目安です。頻度を長くすると、エクステンションの処理時間枠が広くなります。

対象を選択して、またはすべて無効にします。

store.disableBackgroundDelivery(for: [.transactions])
store.disableAllBackgroundDelivery()
バックグラウンド配信エクステンション

Xcode でバックグラウンド配信エクステンションのターゲット(Background Delivery Extension テンプレート)を作成します。2 つの非同期エントリーポイントをエクステンション型に直接実装し、必要な作業の保存が完了してから didReceiveData(for:) から戻るようにします。

import FinanceKit

@main
struct 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() }
}

よくある間違い

1. データが利用できないときに API を呼び出す

してはいけないこと -- 利用可能かどうかの確認を省略する:

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

実施すること -- まず利用可能かどうかを確認する:

guard FinanceStore.isDataAvailable(.financialData) else {
    showUnavailableMessage()
    return
}
let status = try await FinanceStore.shared.requestAuthorization()
2. クレジット/デビットの指標を無視する

してはいけないこと -- 金額を符号付きの値として扱う:

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

実施すること -- 指標を適用する:

let amount = transaction.transactionAmount.amount
let signed = transaction.creditDebitIndicator == .debit ? -amount : amount
3. データ制限エラーに対処しない

してはいけないこと -- 認可されたアクセスが継続すると想定する:

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

実施すること -- FinanceError を捕捉する:

do {
    let transactions = try await store.transactions(query: query)
} catch let error as FinanceError {
    if case .dataRestricted = error { showDataRestrictedMessage() }
}
4. 再開可能な履歴をスナップショットに置き換える

上記の標準的な取引履歴ループを使用し、ローカルでの削除とアップサートがコミットされた後にのみ、各 newToken を保存します。ストリーム/アカウントごとに個別のトークンを保持し、無効なトークンのエラーが発生した場合は、該当するスコープのみを再同期します。

5. 負債口座のクレジット/デビットを誤解する

資産口座と負債口座のどちらでも、.debit は出金を表します。ただし、.credit の意味は異なります。資産口座では入金を表し、負債口座では利用可能な与信額を増やす支払いや返金を表します。解釈の一覧については、references/financekit-patterns.md を参照してください。

レビューチェックリスト

  • API を呼び出す前に FinanceStore.isDataAvailable(.financialData) を確認している
  • アプリの適格性を確認している:Finance カテゴリ、米国または英国での App Store iPhone 配信、財務管理機能、組織アカウント、Account Holder による申請
  • アプリのバンドル ID に対して com.apple.developer.financekit エンタイトルメントを申請し、承認を受けている
  • Info.plist で NSFinancialDataUsageDescription を設定し、明確で具体的なメッセージを記載している
  • すべてのケース(.authorized、.denied、.notDetermined)で認可ステータスを処理している
  • FinanceError.dataRestricted を捕捉し、適切に処理している
  • CreditDebitIndicator を金額に正しく適用している(符号付きの値として扱っていない)
  • 再開可能なクエリのために履歴トークンを保存している
  • FinanceError.historyTokenInvalid が発生した場合、トークンを破棄し、該当するストリームを直ちに再同期している
  • 継続的な同期計画に、取引だけでなく、認可されたアカウント、残高、取引が含まれている
  • ライブアップデートが不要な場合、長時間実行するクエリで isMonitoring: false を使用している
  • 全面的な認可が不要な場合は、取引ピッカーを使用している
  • アプリが本当に必要とするデータのみをクエリしている
  • 履歴の変更で削除された ID を、ローカルのアカウント、残高、または取引のストレージから明示的に削除している
  • バックグラウンド配信の呼び出しで iOS 26 の同期 API を使用し、拡張機能がメインアプリと同じ App Group に属している
  • バックグラウンド配信で必要なデータ型(.accounts、.accountBalances、および/または .transactions)をすべて登録している
  • FinanceKit エンタイトルメントをアプリとバックグラウンド配信拡張機能の両方のターゲットに追加している
  • ユーザーがアクセスを取り消した場合、財務データを削除している

参考資料

コメント