Actual 接入 GoCardless 银行同步完整指南:密钥配置、账户关联与同步机制解析
2026/9/12 5:32:47 网站建设 项目流程

Actual 接入 GoCardless 银行同步完整指南:密钥配置、账户关联与同步机制解析

【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual

Actual 是一款本地优先(local-first)的个人财务管理应用,内置了基于 GoCardless(原 Nordigen)开放银行(Open Banking)协议的银行同步能力。本文以 Actual 官方文档为核心,结合仓库内sync-serverdesktop-client的实际实现,完整讲解从 GoCardless 开发者后台申请密钥、在 Actual 中完成服务端配置、将现有或新建账户与银行账户关联,以及同步背后的端到端调用链路与限额说明。读完本文,你将能够独立完成 Actual 的 GoCardless 银行同步配置,并理解其"服务端托管密钥 + Requisition 授权 + 银行适配器归一化"的底层工作原理。

:::note 本功能要求Client(客户端)版本 23.7.0 及以上Server(服务端)版本 23.7.0 及以上。请确认你部署的 Actual 客户端与服务端均满足该版本条件后再进行配置。 :::

:::warning 从2025 年 7 月起,GoCardless 已停止接受新的Bank Account Data账户注册。下文中的注册指引已过时,但如果你已是存量用户,现有账户仍可继续使用,不受影响。 :::

一、GoCardless 银行同步在 Actual 中的定位与架构

在进入操作步骤之前,先明确 GoCardless 在 Actual 中的角色。从源码结构看,GoCardless 是 Actual 五大银行同步提供商(goCardlesssimpleFinpluggyaienableBankingakahu)之一,该列表定义于 bank-sync.ts 的SYNC_PROVIDERS常量中。

整个同步链路分为三层:

  1. 桌面客户端(desktop-client):负责 UI 交互——引导用户输入密钥、选择银行机构、展示授权跳转与账户选择;相关实现见 gocardless.ts。
  2. 同步服务端(sync-server):托管 GoCardless 密钥,向 GoCardless Bank Account Data API 发起请求,并对各银行返回的原始数据进行归一化;核心实现位于 app-gocardless.ts 与 gocardless-service.ts。
  3. 核心引擎(loot-core):通过GOCARDLESS_SERVER配置项将客户端与服务端地址串接起来,见 server-config.ts。

客户端与服务端之间的通信通过一系列 RPC 方法完成,例如gocardless-create-web-token(创建 Requisition 并返回授权链接)、gocardless-poll-web-token(轮询授权状态)、get-banksget-accountstransactions等,均可在 app-gocardless.ts 中找到对应的 HTTP 路由实现。客户端在授权成功后,会弹出select-linked-accounts模态框让用户选择要同步的账户。

二、为 Actual 创建 GoCardless SECRET 与 KEY

2.1 版本前提

在开始之前,再次确认你的 Actual 部署满足:

  • Client 版本 ≥ 23.7.0
  • Server 版本 ≥ 23.7.0

否则界面中不会出现 GoCardless 相关入口,或同步功能无法正常工作。

2.2 创建步骤

  1. 前往 GoCardless 开放银行开发者门户创建账户:https://bankaccountdata.gocardless.com/overview/(对应 Bank Account Data API 服务)。

  2. 登录账户后台,在左侧菜单选择Developers → User secrets

  3. 点击左下角'+ create new'按钮创建新的用户密钥。

    • 务必下载你的密钥文件,因为key(密钥本身)将不会在账户后台再次显示,丢失后只能重新创建;
    • 这对secret_id/secret_key将用于 Actual 建立银行同步连接。

  4. 为密钥输入一个便于识别的名称,然后点击 Create。该名称仅用于你在 GoCardless User secrets 概览页中快速区分不同密钥,不影响 Actual 侧的任何行为。

  5. 下载生成的密钥文件并妥善保存在本地电脑。

  6. 回到 Actual,点击侧边栏底部的"+ Add account"

  7. 在弹出的对话框中选择"Set-up GoCardless for bank-sync"

  8. 按提示输入你的 GoCardlesssecret IDsecret key。这些值会保存在服务端,因此只需输入一次,之后所有账户的同步都会复用这套凭据。

2.3 密钥在服务端的存储与校验(源码级细节)

从源码可以确认密钥的实际用途与校验逻辑:

  • 服务端通过secretsService.get(SecretName.gocardless_secretId)secretsService.get(SecretName.gocardless_secretKey)读取凭据,并以 JSON 序列化结果作为缓存键维护 GoCardless API 客户端实例(gocardless-service.ts),即密钥一旦更新,客户端实例也会随之重建。
  • 服务端暴露POST /status接口,返回configured: goCardlessService.isConfigured(),用于告知客户端当前是否已配置密钥(app-gocardless.ts)。
  • 获取机构列表(POST /get-banks)前,服务端会先调用setToken():若缓存的 JWT 已过期,则用secret_id/secret_key调用 GoCardless 的/api/v2/token/new/换取新的 access token(gocardless-service.ts)。

因此,"只配置一次"背后的实现是:密钥持久化在服务端,客户端仅负责采集与转发

三、将 Actual 账户与 GoCardless 银行账户关联

3.1 两种入口

为账户建立 GoCardless 关联有两种方式:

  • 已有账户:点击该账户,进入右上角的...(kebab 菜单),选择Link Account

  • 新建账户:点击左侧菜单底部的'+ Add account',选择银行同步方式创建新账户。

3.2 关联流程

  1. 点击Link your bank account按钮。

  2. 从列表中选择你的国家与银行,然后点击Link bank in browser按钮。

  3. 点击后浏览器会新开一个标签页,跳转到你的银行,授权 GoCardless 访问你的账户。

  4. 在银行页面选择I agree,同意 GoCardless 访问你的支付账户信息。

  5. 连接成功后,点击 continue 按钮,允许 GoCardless 完成连接建立。

  6. GoCardless 连接你的银行并拉取账户列表期间,界面会显示进度指示器。

  7. 连接建立后,界面会列出可选的银行账户。

  8. 最后一步:选中你要同步的账户,点击Link account完成关联。

3.3 关联背后的 Requisition 机制

"关联"在技术实现上对应 GoCardless 的Requisition(授权请求)概念。整个流程由以下源码环节支撑:

  • 客户端在用户选择机构后,调用gocardless-create-web-token,其中将accessValidForDays固定为90 天(gocardless.ts)。
  • 服务端POST /create-web-token会调用goCardlessService.createRequisition()(app-gocardless.ts),其内部依次完成:刷新 token → 查询机构信息 → 判断机构是否支持account_selection/separate_continuous_history_consent等特性 → 携带accessValidForDaysmaxHistoricalDays等参数初始化 Requisition(gocardless-service.ts)。若首次请求失败,会自动回退为accessValidForDays = 90maxHistoricalDays = 89重试。
  • 授权完成后,客户端调用gocardless-poll-web-token轮询状态,成功后弹出账户选择界面,最终以select-linked-accounts模态框确认同步账户(gocardless.ts)。
  • 服务端在拉取账户时会校验 Requisition 状态必须为LN(Linked),否则抛出RequisitionNotLinked异常(gocardless-service.ts)。
  • 出于隐私考虑,服务端返回账户列表时会用sha256String()对 IBAN 做哈希处理后再下发客户端(app-gocardless.ts)。

需要特别注意的是:Requisition 授权是有有效期的。GoCardless 通过 End User Agreement 控制访问时长,当协议过期时,同步会返回ITEM_LOGIN_REQUIRED错误(status: 'expired'),此时需要重新走一遍授权流程(app-gocardless.ts)。

四、同步操作与最佳实践

4.1 Actual 会自动与银行同步吗?

目前不会。Actual 暂不支持银行账户的自动同步,你需要手动触发:进入"All Accounts"页面,点击"Sync"按钮。

手动同步的语义可以从源码中得到印证:同步本质上是让服务端调用 GoCardlessGET /accounts/{id}/transactions/(支持date_from/date_to参数)拉取交易,再经银行适配器归一化后返回给客户端导入(gocardless-api.ts)。

4.2 从零开始使用 GoCardless 的最佳姿势

如果你是第一次用 Actual 搭配 GoCardless,不建议拉取历史数据——这曾给不少用户在后续对账(reconciliation)时带来困扰。更稳妥的流程是:

  1. 在 Actual 中创建账户,填写最近某一天的准确期初余额(opening account balance)
  2. 按上文步骤将该账户与 GoCardless 关联;
  3. 手动同步账户。你会发现只有期初余额日期之后的交易被导入,对账因此变得非常简单。

这一建议与实现机制高度吻合:同步交易时可以携带startDate/endDate参数进行区间拉取,服务端在拉取后还会调用calculateStartingBalance依据当前余额与已导入交易反推期初余额(gocardless-service.ts),保证导入数据的连续性。

4.3 同步频率限额

在 GoCardless 免费档(free tier)下:

  • 每月最多连接50 家银行(如果一家银行下挂多个账户,如信用卡+借记卡,仍按1 次连接计);
  • 每家银行每天最多同步4 次,该限制为每日限制,月度不设上限;
  • 更多细节可查阅 GoCardless 官方 FAQ 中 "Bank Account Data API Usage" 主题(如何计算用量)。

五、深入:银行适配器与交易归一化(进阶)

5.1 BankFactory 与银行专属类

每家银行返回的字段格式并不完全一致(账户命名、交易附言、余额类型各有差异),Actual 通过BankFactory + 银行适配器模式解决这一问题:

  • BankFactory(account.institution_id)根据机构 ID 返回对应的银行适配器实例(bank-factory.ts);
  • 仓库内置了大量银行适配器,覆盖 mBank、Revolut、ING、Nationwide、BoursoBank 等机构,每个适配器文件以机构ID_机构BIC命名,例如revolut_revolt21.tsmbank_retail_brexplpw.ts(见 banks 目录);
  • 每个适配器需要实现IBank接口中的四个方法:normalizeAccountnormalizeTransactionsortTransactionscalculateStartingBalance

5.2 normalizeTransaction 的正确写法

normalizeTransaction是最常用的覆写方法,它决定返回给客户端的数据形态。官方推荐的实现模式如下(integration-bank.ts 与 README):

  1. 需要修改交易字段时,不要改动原始 transaction 对象,先做浅拷贝;
  2. 函数末尾调用Fallback.normalizeTransaction(transaction, booked, editedTrans),把修改后的对象作为第 3 个参数传入,由兜底实现统一计算日期、收款人名称(payeeName)与备注(notes)。

示例代码:

import Fallback from './integration-bank'; export default { // ... normalizeTransaction(transaction, booked) { // 创建 transaction 的浅拷贝 const editedTrans = { ...transaction }; // 在拷贝上进行需要的修改 editedTrans.remittanceInformationUnstructured = transaction.remittanceInformationStructured; // 调用兜底方法,传入修改后的对象作为第 3 个参数, // 该方法会基于你的修改计算日期、payeeName 与 notes 字段, // 同时保留原始字段供 UI 映射使用 return Fallback.normalizeTransaction(transaction, booked, editedTrans); } // ... };

兜底实现(integration-bank.ts)中值得注意的细节:交易日期按date → bookingDate → bookingDateTime → valueDate → valueDateTime的顺序取值,全部缺失则过滤掉该笔交易(等待银行后续处理完成后再次导入);备注按notes → remittanceInformationUnstructured → remittanceInformationUnstructuredArray依次回退(integration-bank.ts)。

5.3 新增银行适配器的工作流

如果内置适配器无法正确处理某家银行,官方 README(app-gocardless/README.md)给出了完整的新增流程:

  1. 找到目标银行的机构标识符(institution id,注意旧版参考表已不再维护);
  2. 启动前端与后端服务;
  3. 在前端创建关联账户并选择该机构,触发数据抓取,后端会输出该机构原始数据的日志(包括账户属性、前 10 条交易、余额等 JSON 样例),据此填充银行类逻辑;
  4. 参照app-gocardless/banks下已有示例新建银行类,文件名与类名遵循既有模式(以机构 ID 命名);
  5. 按日志数据实现normalizeAccountnormalizeTransactionsortTransactionscalculateStartingBalance中必要的函数(不需要全部实现);
  6. 将新银行注册到BankFactory中;
  7. 为新银行补充测试用例(仓库中每个银行适配器都有对应的.spec.ts测试,例如 revolut_revolt21.spec.ts)。

六、常见问题速查(FAQ)

问题答案
Actual 会自动同步银行数据吗?不会,需在 "All Accounts" 页面手动点击 "Sync"
免费档每月可连接多少家银行?每月最多 50 家,一家银行含多账户仍计 1 次
每天可同步几次?每家银行每天最多 4 次(每日限额,月度不设限)
从零开始怎么用最省心?设置近期期初余额 → 关联 → 同步,仅导入期初后的交易
密钥需要重复输入吗?不需要,secret 保存在服务端,仅需配置一次
授权过期了怎么办?重新执行关联流程(同步会提示 ITEM_LOGIN_REQUIRED)

七、本文要点回顾

  • GoCardless 同步需要 Client 与 Server 均 ≥ 23.7.0,且自 2025 年 7 月起 GoCardless 不再接受新的 Bank Account Data 账户注册,存量用户不受影响;
  • 密钥在 GoCardless 开发者后台创建后仅可下载一次,配置到 Actual 后持久化保存在服务端;
  • 账户关联本质是创建 GoCardless Requisition 并完成银行侧授权,授权受 End User Agreement 有效期约束;
  • 同步为手动触发,免费档每月 50 家银行、每日每行 4 次的限额需要留意;
  • 若需扩展新银行,可参照BankFactory + 银行适配器 + normalizeTransaction 兜底模式的既有架构进行集成。

【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询