关于 connector-quickbooks
面向 QuickBooks Online Accounting API v3 精选实体的 Motoko 客户端库。支持客户、发票、付款、项目和账户操作,并使用 OAuth 2.0 认证。
主要功能
- 01为五种 QBO 实体提供 Motoko 绑定
- 02创建和获取支持的记录
- 03发送发票
- 04查询 QBO 记录
- 05使用 OAuth 2.0 Bearer 令牌认证
适用场景
- 01创建或更新客户和项目记录
- 02获取并发送发票
- 03记录付款并查询会计数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ caffeinelabs/ skills --skill connector-quickbooks安装后在 Claude Code 中输入/connector-quickbooks确认已加载。
详细介绍
quickbooks-client
QuickBooks Online Accounting API v3 的精选子集的 Motoko 绑定,涵盖 QuickBooks Online Accounting API
v3 中的:
Customer、Invoice、Payment、Item、Account。架构根据 Intuit 的官方 XSD 转录。全部 12 个操作都位于 Apis/DefaultApi.mo:
saveCustomer/getCustomer、saveItem/getItem、saveAccount/getAccount、
saveInvoice/getInvoice/sendInvoice、savePayment/getPayment 和 query_
(末尾带下划线——query 在 Motoko 中是保留字)。
触发短语
任何提及以下内容的请求都可使用此技能:QuickBooks、QBO、发票、 “向客户收费”、“发送发票”、记录/接收付款、客户、 会计、记账、科目表、商品/产品/服务、 “同步到 QuickBooks”。
此子集不包含: Estimate、Bill、Vendor、PurchaseOrder、CreditMemo 以及 QBO 实体集中的其他内容——该规范是手动精选的,仅包含 Customer、Invoice、 Payment、Item 和 Account。如果请求需要其他实体,请明确说明, 不要将其映射到 Invoice;必须先根据 Intuit 的 XSD 重新生成规范。
QuickBooks 身份验证的工作方式(接入前阅读)
QBO 使用 OAuth 2.0 Authorization Code——没有静态 API 密钥。每位
最终用户都会授权其 QuickBooks 公司;应用将授权码兑换为短时有效的
Bearer 访问令牌(约 1 小时),并在调用时将其传递给客户端。令牌过期后,
API 会返回 HTTP 401——请返回 #Err("auth_expired") 结果,并在链下重新进行身份验证。
每次调用都需要传递两个标识符:
realmId——QuickBooks 公司 ID,在 OAuth 握手期间获取。 它是每个操作的第一个参数。minorversion——API 次版本(默认为"75");传入""可省略, 并使用账户默认值。
令牌兑换本身也是对 Intuit 令牌端点的链上外呼, 并且需要应用的 Client Secret。有两个风险(与 googlemail 连接器相同):
- 兑换操作也要设置
is_replicated = ?false——令牌响应具有 非确定性,因此在约 13 个副本上重复执行会导致 IC 共识失败。 - 导出的源代码会泄露 Client Secret——Caffeine 可以将应用导出为
.zip, 或导出到公开的 GitHub 仓库,届时密钥也会一同泄露。请尽量缩小其适用范围,并在共享源代码后轮换密钥。
请将每位用户的刷新令牌/会话令牌持久化到升级后仍保留的存储(稳定内存),以免
重新部署后必须重新进行身份验证。OAuth scope:com.intuit.quickbooks.accounting。
创建、更新与删除(重要的 QBO 语义)
- 创建和更新都使用
save<Entity>(POST)操作。 当请求体包含实体的Id和当前SyncToken(先通过get<Entity>获取)时,这是一次更新;部分更新时设置sparse = ?true。如果没有Id/SyncToken,则是创建。 - 删除(仅限交易:
Invoice、Payment):调用saveInvoice/savePayment,设置operation = ?#delete,并在请求体中携带Id+SyncToken。operation参数是可选的(?SaveInvoiceOperationParameter),不是单独的变体。正常创建或更新时传入null——客户端会完全省略查询参数,这正是 QBO 所需的行为;规范写明:“创建/更新时省略;‘delete’ 会删除发票。”通常只需要?#delete。名称列表实体(Customer、Item、Account)不可删除——改为设置Active = ?false停用(它们的save*操作不接受operation参数)。 - 响应会被包装:
getCustomer返回带有Customer字段(以及time)的CustomerResponse;其他实体的结构相同。query_返回QueryResponse,其QueryResponse字段包含各实体类型的数组。操作名称是query_,不是query。 - 发票行和付款行是不同的类型。
Invoice.Line是[Line],其中DetailType是必填项。Payment.Line是[PaymentLine]——{Amount, LinkedTxn, …},没有DetailType,这正是 Intuit 在将付款应用于发票时实际发送的内容。在 0.2.0 之前,它们共用同一种类型,导致每个付款响应都无法解码。
前端界面(两个页面)
与任何按用户区分的 OAuth 连接器一样,应用需要两个界面:
- 一个由管理员权限控制的 Intuit 应用配置页面——Client ID 和 Client Secret 由管理员设置一次,适用于整个 canister;绝不能向普通用户公开 Client Secret。
- 一个按用户区分的 OAuth 2.0 握手页面——每位用户连接自己的 QuickBooks 公司(获取其
realmId+ token);遇到 HTTP 401 时重新提示用户授权。
用法
import { saveInvoice; getInvoice; sendInvoice; saveCustomer }
"mo:quickbooks-client/Apis/DefaultApi";
import Invoice "mo:quickbooks-client/Models/Invoice";
import { defaultConfig } "mo:quickbooks-client/Config";
let cfg = {
defaultConfig with
auth = ?#bearer "<off-chain OAuth2 access token>";
is_replicated = ?false; // non-replicated: required for writes; reads too
};
let realmId = "<company id from OAuth handshake>";
// Build an invoice: one sales line, $100, item "1", billed to customer "58".
// Invoice has no required fields → init {} then record-update the optionals.
// NOTE: DetailType is REQUIRED on a Line, and its variants are lowercase.
let inv = {
Invoice.init {} with
CustomerRef = ?{ value = "58"; name = null };
Line = ?[ {
DetailType = #salesitemlinedetail; // required; lowercase variant
Amount = ?100.0;
Description = ?"Consulting";
SalesItemLineDetail = ?{ ItemRef = ?{ value = "1"; name = null };
Qty = ?1.0; UnitPrice = ?100.0; TaxCodeRef = null; ServiceDate = null };
Id = null; LineNum = null; LinkedTxn = null;
} ];
};
// saveInvoice(config, realmId, invoice, minorversion, operation)
// `operation` is optional: null omits it — the create/update path.
let created = await* saveInvoice(cfg, realmId, inv, "75", null);
// Email it to the customer (config, realmId, invoiceId, sendTo, minorversion):
// ignore await* sendInvoice(cfg, realmId, "<invoiceId>", "[email protected]", "75");
说明
- 写入操作请使用
is_replicated = ?false(save*、sendInvoice、通过operation = ?#delete删除)。这些外调不是幂等的,而且 QBO 的响应具有非确定性;在复制模式下,每个副本都会发出请求,导致创建重复的发票/付款并使 IC 共识失败。读取操作(get*、query_)也使用?false(单个节点执行,成本约低 13 倍)。 - 更新需要最新的
SyncToken——始终先调用get<Entity>,将其SyncToken复制到请求体中,然后调用save<Entity>。令牌过期会导致 HTTP 400(“stale object”)。 minorversion默认为"75";传入""可省略。- QBO 将错误作为
ErrorResponse返回,其列表字段为Fault.Error_——注意末尾的下划线,因为Error是保留字——因此应将Fault.Error_[0].Message返回给调用方;切勿盲目重试写入操作。 - 沙盒与生产环境:开发期间,将客户端主机指向 Intuit 沙盒公司使用的
sandbox-quickbooks.api.intuit.com(在Config中编辑服务);生产环境使用quickbooks.api.intuit.com。