Connector Quickbooks

作者 caffeinelabs362f51ae96f0無授權條款2 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫今天更新

Use the `quickbooks-client` mops package whenever the user asks the canister to create/read/update invoices, customers, payments, items, or chart-of-account entries in QuickBooks Online (QBO), email an invoice, or run a QuickBooks query. The package wraps the QuickBooks Online Accounting API v3 at `https://quickbooks.api.intuit.com` via outbound HTTPS calls.

AI 產生的概覽

用於 QuickBooks Online 會計 API v3 精選介面的 Motoko 用戶端綁定。

功能
提供十二個 QuickBooks Online 會計 API v3 操作的 Motoko 綁定,涵蓋 Customer、Invoice、Payment、Item 與 Account 實體,包含建立、讀取、更新、刪除與查詢呼叫。文件說明了 OAuth 2.0 授權碼驗證、realmId 與 minorversion 的處理方式,以及 QBO 的建立與更新語意。也描述了所需的前端介面:管理員設定頁面與每位使用者的 OAuth 授權頁面。
適用情境
當容器需要在 QuickBooks Online 中建立、讀取、更新或刪除發票、客戶、付款、項目或會計科目表項目,寄送發票電子郵件,或執行 QuickBooks 查詢時使用。也適用於串接 QuickBooks OAuth 驗證或排查 QBO API 錯誤。
執行需求
需要 quickbooks-client mops 套件(~0.2.0)與 Motoko 執行環境。需要對 QuickBooks Online API 與 Intuit 權杖端點的對外 HTTPS 連線、Intuit 應用程式的 Client ID 與 Client Secret,以及可產生 realmId 與 Bearer 存取權杖的每位使用者 OAuth 2.0 憑證。不含指令碼,僅為說明文件。

quickbooks-client

Motoko bindings for a curated slice of the QuickBooks Online Accounting API v3: Customer, Invoice, Payment, Item, Account. Schemas were transcribed from Intuit's official XSD. All 12 operations live in Apis/DefaultApi.mo: saveCustomer/getCustomer, saveItem/getItem, saveAccount/getAccount, saveInvoice/getInvoice/sendInvoice, savePayment/getPayment, and query_ (trailing underscore — query is a reserved word in Motoko).

Trigger phrases

Reach for this skill on any request mentioning: QuickBooks, QBO, invoice, "bill a customer", "send an invoice", record/receive a payment, customer, accounting, bookkeeping, chart of accounts, item/product/service, "sync to QuickBooks".

Not in this slice: Estimate, Bill, Vendor, PurchaseOrder, CreditMemo and the rest of the QBO entity set — the spec is hand-curated to Customer, Invoice, Payment, Item and Account. If a request needs one of the others, say so rather than mapping it onto Invoice; the spec has to be re-spun from Intuit's XSD first.

How QuickBooks authentication works (read before wiring)

QBO uses OAuth 2.0 Authorization Code — there is no static API key. Each end-user authorises their QuickBooks company; the app exchanges the authorization code for a short-lived Bearer access token (~1 hour) and passes it to the client at call time. On expiry the API returns HTTP 401 — surface a #Err("auth_expired") result and re-authenticate off-chain.

Two identifiers travel with every call:

  • realmId — the QuickBooks company id, obtained during the OAuth handshake. It is the first argument to every operation.
  • minorversion — the API minor version (default "75"); pass "" to omit and use the account default.

The token exchange is itself an on-chain outcall to Intuit's token endpoint and needs the app's Client Secret. Two hazards (identical to the googlemail connector):

  • is_replicated = ?false on the exchange too — the token response is non-deterministic, so a replicated exchange duplicates across ~13 replicas and fails IC consensus.
  • The Client Secret leaks with exported source — Caffeine can export the app to a .zip or public GitHub repo, carrying the secret along. Scope it minimally and rotate if the source is shared.

Persist the per-user refresh/session token across upgrades (stable memory) so a redeploy doesn't force re-authentication. OAuth scope: com.intuit.quickbooks.accounting.

Create vs. update vs. delete (important QBO semantics)

  • Create and update BOTH use the save<Entity> (POST) operation. It's an update when the body carries the entity's Id and current SyncToken (fetch them first via get<Entity>); set sparse = ?true for a partial update. Without Id/SyncToken it's a create.
  • Delete (transactions only — Invoice, Payment): call saveInvoice / savePayment with operation = ?#delete and a body carrying Id + SyncToken. The operation argument is optional (?SaveInvoiceOperationParameter), not a bare variant. Pass null for a normal create or update — the client then omits the query parameter entirely, which is what QBO wants; the spec says "Omit for create/update; 'delete' removes the invoice". Only ?#delete is routinely needed. Name-list entities (Customer, Item, Account) are not deletable — set Active = ?false to deactivate instead (their save* ops take no operation argument).
  • Responses are wrapped: getCustomer returns CustomerResponse with a Customer field (and a time); same shape per entity. query_ returns a QueryResponse whose QueryResponse field holds arrays per entity type. The operation is query_, not query.
  • Invoice lines and Payment lines are different types. Invoice.Line is [Line], where DetailType is required. Payment.Line is [PaymentLine] — {Amount, LinkedTxn, …} with no DetailType, which is what Intuit actually sends when a payment is applied to invoices. They were one shared type until 0.2.0, and that made every payment response fail to decode.

Frontend surfaces (two pages)

Like any per-user OAuth connector, an app needs two surfaces:

  • An admin-gated Intuit app configuration page — the Client ID and Client Secret are canister-wide, set once by an admin; never expose the Client Secret to ordinary users.
  • A per-user OAuth 2.0 handshake page — each user connects their own QuickBooks company (yielding their realmId + token); re-prompt on HTTP 401.

Usage

mo:quickbooks-client
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");

Notes

  • Use is_replicated = ?false for writes (save*, sendInvoice, delete via operation = ?#delete). These outcalls are non-idempotent and QBO's response is non-deterministic; in replicated mode every replica issues the request — creating duplicate invoices/payments and failing IC consensus. Reads (get*, query_) also use ?false (one node, ~13× cheaper).
  • Update needs a fresh SyncToken — always get<Entity> first, copy its SyncToken into the body, then save<Entity>. A stale token → HTTP 400 ("stale object").
  • minorversion defaults to "75"; pass "" to omit.
  • QBO returns errors as an ErrorResponse whose list field is Fault.Error_ — note the trailing underscore, Error being reserved — so surface Fault.Error_[0].Message to the caller; never blind-retry a write.
  • Sandbox vs production: point the client host at sandbox-quickbooks.api.intuit.com for the Intuit sandbox company during development (edit the server in Config), and quickbooks.api.intuit.com for production.

來源與署名

來源:caffeinelabs/skills位於skills/connector-quickbooks提交362f51a

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架