PAYATHON 2026

QuickBooks Online 与 QBMS 定期付款的客户关联方法

支付老李

结论

QBO 客户与 QBMS recurring payment 不会自动建立跨系统关联,需要由集成应用保存映射关系:

  • QBO:保存 realmId,以及创建客户后返回的 Customer.Id
  • QBMS:保存创建定期付款后返回的 recurring payment ID
  • 如果当前 QBMS API 的请求模型提供 CustomerID、ReferenceID 或类似的商户自定义字段,可以写入 QBO 客户 ID 或应用生成的关联键

QBO 不支持的 QBMS “Reconciliation” 功能,是指桌面版中自动下载、匹配和过账商户交易的集成功能。这项限制不影响应用保存客户与定期付款的对应关系,影响的是后续自动记账。

推荐的数据关系

不要只保存 QBO Customer.Id。这个 ID 只在对应的公司文件中有效,因此至少要保存:

realmId + qboCustomerId -> qbmsMerchantAccount + recurringPaymentId

例如:

{
  "realmId": "1234567890",
  "qboCustomerId": "58",
  "qbmsMerchantAccountKey": "merchant-account-a",
  "qbmsRecurringPaymentId": "rp_abc123",
  "status": "ACTIVE"
}

如果 QBMS 的 recurring payment 请求允许传入商户自定义的客户标识,可以使用:

qbo:{realmId}:{qboCustomerId}

使用前要确认该字段对长度、字符集和唯一性的限制。如果字段容纳不了这个值,可以生成较短的内部关联键,再由数据库保存完整映射。

实现步骤

1. 在 QBO 中创建客户

调用 QBO Accounting API 创建 Customer,然后保存响应中的 Id:

POST /v3/company/{realmId}/customer
Content-Type: application/json
Authorization: Bearer {accessToken}
{
  "DisplayName": "Example Customer",
  "PrimaryEmailAddr": {
    "Address": "customer@example.com"
  }
}

响应中的相关字段类似:

{
  "Customer": {
    "Id": "58",
    "DisplayName": "Example Customer",
    "SyncToken": "0"
  }
}

后续关联应使用 Customer.Id,不要使用客户名称或电子邮箱,因为名称和邮箱都可能修改,也不保证唯一。

2. 生成关联键

function buildCustomerCorrelationKey(realmId, qboCustomerId) {
  return `qbo:${realmId}:${qboCustomerId}`;
}

如果当前使用的 QBMS SDK/API 支持 CustomerID 一类字段,可以将这个关联键传入。具体字段名称和格式取决于 QBMS API 版本,应以对应版本的请求 schema 为准。

下面的代码仅用于说明集成逻辑,并不代表任何版本 QBMS SDK 的完整请求结构:

const correlationKey = buildCustomerCorrelationKey(
  realmId,
  qboCustomer.Id
);

const recurringRequest = {
  customerReference: correlationKey,
  amount: "49.00",
  frequency: "MONTHLY",
  startDate: "2026-10-01",
  paymentMethodToken: paymentMethodToken
};

const recurringResult =
  await qbmsClient.createRecurringPayment(recurringRequest);

其中,customerReference、frequency 和 paymentMethodToken 都是占位名称。实际接入时,必须换成所用 SDK/API 定义的字段。

3. 保存 QBMS 返回的定期付款 ID

创建成功后,将 QBMS 返回的 recurring payment ID 与 QBO 客户 ID 一并持久化:

await customerPaymentMapping.insert({
  realmId,
  qboCustomerId: qboCustomer.Id,
  qbmsMerchantAccountKey,
  qbmsRecurringPaymentId: recurringResult.id,
  correlationKey,
  status: "ACTIVE"
});

建议为以下组合设置唯一约束:

(realmId, qboCustomerId, qbmsRecurringPaymentId)

还应保存创建状态和最后同步时间,以便处理重试、暂停和取消操作。

4. 将成功扣款记入 QBO

QBMS 完成定期扣款,不代表 QBO 中已经生成会计交易。应用还要根据业务模型写入 QBO:

  • 已有发票:创建 Payment,并将其关联到对应的 Invoice
  • 没有发票,属于直接销售:通常创建 SalesReceipt
  • 支付手续费:根据实际结算方式单独记录
  • 多笔款项合并结算:正确处理 Deposit 或未存入资金账户

例如,为指定客户创建付款时,需要明确设置客户引用:

{
  "CustomerRef": {
    "value": "58"
  },
  "TotalAmt": 49.00
}

如果这笔付款用于结清发票,还要通过 Line.LinkedTxn 指向相应的 Invoice。仅设置 CustomerRef,系统不会自动选择应收发票。

Reconciliation 限制意味着什么

建立客户外键并不依赖这里所说的 Reconciliation。该功能主要用于:

  • 自动获取 QBMS 交易
  • 将结算记录与 QuickBooks 中的销售或收款匹配
  • 自动处理批次、存款和手续费
  • 减少人工对账

因此,这项限制不会阻止应用执行以下操作:

  • 在 QBO 创建客户
  • 在 QBMS 创建 recurring payment
  • 在应用数据库中保存两者的映射
  • QBMS 扣款成功后,调用 QBO API 创建会计记录

不过,这些步骤不会由 QBMS 和 QBO 自动替应用完成。

需要特别注意的事项

不要用名称关联客户

DisplayName、姓名和邮箱都可能变化。稳定标识应为:

realmId + Customer.Id

做好幂等处理

网络超时可能发生在 QBMS 已经创建定期付款,但应用还没有收到响应的时候。此时直接重试,可能会创建两个计划。应用应在创建前后通过关联键查询现有记录,并记录请求状态。

将扣款写入 QBO 时,也要保存 QBMS transaction ID,避免 webhook 重发或重复轮询造成重复入账。

不要自行保存银行卡号

优先使用 QBMS 提供的托管支付页面、令牌化支付方式,或其他符合当前接口要求的安全机制。不要将完整卡号或 CVV 写入日志、数据库或自定义关联字段。

区分 recurring payment ID 与交易 ID

一个 recurring payment 通常会产生多次实际扣款:

一个 recurring payment ID
    ├── 第一次 transaction ID
    ├── 第二次 transaction ID
    └── 第三次 transaction ID

应用需要分别保存计划级映射和每次交易的映射。取消定期付款,也不能代替冲正已经完成的交易。

不要假设扣款成功就已完成记账

应以 QBMS 返回的最终交易状态为准,同时处理授权失败、退款、撤销和拒付等情况。确定业务事件后,再创建或更新对应的 QBO 交易。

这套集成不依赖 Reconciliation。应用需要建立可靠的映射层,并以幂等方式将每次 QBMS 支付结果同步到正确的 QBO 客户和会计交易中。

备注:内容仅供参考。