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 客户和会计交易中。
备注:内容仅供参考。
