mcpField
C
connector-quickbooks

connector-quickbooks

github.com/caffeinelabs其他26.6k 次安装6 天前更新
代码仓库

关于 connector-quickbooks

面向 QuickBooks Online Accounting API v3 精选实体的 Motoko 客户端库。支持客户、发票、付款、项目和账户操作,并使用 OAuth 2.0 认证。

主要功能

  1. 01为五种 QBO 实体提供 Motoko 绑定
  2. 02创建和获取支持的记录
  3. 03发送发票
  4. 04查询 QBO 记录
  5. 05使用 OAuth 2.0 Bearer 令牌认证

适用场景

  1. 01创建或更新客户和项目记录
  2. 02获取并发送发票
  3. 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。

评论

支持 Markdown