如何使用 Zoho Books API 生成支付链接并通过 SMS 发送发票?
结论
仅靠 Zoho Books API,通常无法完整实现“生成支付链接 → 通过 SMS 发送 → 支付回调 → 创建已付款发票 → 再通过 SMS 发送发票”这一整套流程。
实际应用中,需要组合使用三类服务:
- 由支付网关或支付链接服务生成付款页面;
- 由 SMS 服务商发送支付链接和发票链接;
- 通过 Zoho Books API 创建发票,并在确认收款后登记 Customer Payment。
如果账户已经启用 Zoho Books 支持的在线支付网关,可以先创建发票,再把发票的在线付款页面链接发给客户。不过,链接字段和相关能力会受到地区、版本、网关及组织配置的影响,具体应以当前 API 响应和官方文档为准。
推荐流程
更稳妥的做法是在客户付款前创建发票,具体顺序如下:
- 在 Zoho Books 中创建草稿或待付款发票;
- 使用支付服务创建支付链接,并将
invoice_id写入支付订单的 metadata; - 通过第三方 SMS 服务发送支付链接;
- 接收支付服务发送的 webhook;
- 验证 webhook 签名,确认交易确实成功;
- 调用 Zoho Books Customer Payments API,将付款应用到对应发票;
- 获取发票 PDF 或客户可以访问的发票页面;
- 通过 SMS 发送安全的发票下载链接。
这套流程可以让支付订单、Zoho Books 发票和后续退款保持稳定的对应关系,也能减少收款后无法匹配客户或发票的问题。
创建 Zoho Books 发票
以下示例省略了 OAuth 获取过程。实际调用时,需要使用有效的 Zoho OAuth access token,并根据数据中心调整 API 域名。
curl --request POST \
'https://www.zohoapis.com/books/v3/invoices?organization_id=YOUR_ORGANIZATION_ID' \
--header 'Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": "YOUR_CUSTOMER_ID",
"date": "2026-08-31",
"line_items": [
{
"item_id": "YOUR_ITEM_ID",
"quantity": 1,
"rate": 299.00
}
],
"notes": "Payment order: order_20260831_001"
}'
收到响应后,保存其中的 invoice_id、发票编号和应付金额。不要只靠备注里的订单号进行匹配,最好同时在自己的数据库中保存以下对应关系:
internal_order_id
zoho_invoice_id
payment_order_id
customer_id
expected_amount
currency
payment_status
生成并发送支付链接
支付链接应由实际负责收款的支付服务生成,例如已经接入的支付网关、托管收银台或支付链接产品。创建支付订单时,需要把 Zoho Books 的 invoice_id 写入 metadata。
下面是与厂商无关的示意代码。实际使用时,需要将字段名称替换为所选支付服务的真实 API 字段:
const paymentLink = await paymentProvider.createPaymentLink({
amount: 29900,
currency: "CNY",
description: "Invoice INV-000123",
metadata: {
internal_order_id: "order_20260831_001",
zoho_invoice_id: "YOUR_INVOICE_ID"
},
callback_url: "https://example.com/webhooks/payment"
});
await smsProvider.send({
to: "+8613800000000",
message: `您的订单待支付,请打开:${paymentLink.url}`
});
金额单位必须按照支付服务的定义处理。有些接口使用元,有些使用分等最小货币单位,因此不能根据示例直接认定 29900 代表 299.00。
支付成功后登记收款
收到 webhook 后,先验证签名,再调用支付服务的查询接口,核实交易状态、金额、币种和商户订单号。确认无误后,调用 Zoho Books Customer Payments API,将款项应用到对应发票。
curl --request POST \
'https://www.zohoapis.com/books/v3/customerpayments?organization_id=YOUR_ORGANIZATION_ID' \
--header 'Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": "YOUR_CUSTOMER_ID",
"payment_mode": "Online Payment",
"amount": 299.00,
"date": "2026-08-31",
"reference_number": "PAYMENT_PROVIDER_TRANSACTION_ID",
"invoices": [
{
"invoice_id": "YOUR_INVOICE_ID",
"amount_applied": 299.00
}
]
}'
全额付款登记成功后,发票通常会变为已付款状态。不要直接修改发票状态来代替创建 Customer Payment,否则应收账款、付款记录和对账信息可能不完整。
如果业务必须采用“先收款、后开票”的方式,也可以在 webhook 确认付款成功后创建发票,然后立即创建 Customer Payment,并将款项应用到这张发票。不过,付款前必须保存好客户、商品、税率、币种和开票主体等信息。
通过 SMS 发送发票
SMS 适合发送短文本和 URL,不适合直接发送 PDF 附件。Zoho Books 的邮件发送能力也不等同于 SMS 发送能力,所以通常需要自行完成以下处理:
- 获取 Zoho Books 提供的客户可访问发票链接;
- 或下载发票 PDF,并保存到受保护的对象存储;
- 生成带有效期的签名 URL;
- 通过 SMS 服务商发送该 URL。
示意代码:
const invoiceUrl = await createTemporaryInvoiceUrl({
invoiceId: "YOUR_INVOICE_ID",
expiresInSeconds: 86400
});
await smsProvider.send({
to: "+8613800000000",
message: `您的付款已收到。发票请在 24 小时内下载:${invoiceUrl}`
});
不要把需要 Zoho OAuth 登录才能访问的 API 地址直接发给客户。客户无法使用应用的 access token,在 URL 中携带 token 还会造成严重的凭据泄露风险。
Webhook 处理示例
app.post("/webhooks/payment", async (req, res) => {
const event = verifyPaymentWebhook(req);
if (event.type !== "payment.succeeded") {
return res.sendStatus(200);
}
const payment = await paymentProvider.getPayment(event.payment_id);
if (
payment.status !== "succeeded" ||
payment.amount !== 29900 ||
payment.currency !== "CNY"
) {
return res.sendStatus(400);
}
const zohoInvoiceId = payment.metadata.zoho_invoice_id;
if (await hasProcessedPayment(payment.id)) {
return res.sendStatus(200);
}
await createZohoCustomerPayment({
customerId: "YOUR_CUSTOMER_ID",
invoiceId: zohoInvoiceId,
amount: 299.00,
referenceNumber: payment.id
});
await markPaymentAsProcessed(payment.id);
const invoiceUrl = await createTemporaryInvoiceUrl({
invoiceId: zohoInvoiceId,
expiresInSeconds: 86400
});
await smsProvider.send({
to: payment.customer_phone,
message: `付款成功,发票下载地址:${invoiceUrl}`
});
res.sendStatus(200);
});
在生产环境中,数据库状态更新、Zoho Books 调用和短信发送都应设计成可重试任务。短信发送失败时,不应回滚已经确认的收款记录。
注意事项
防止重复记账
支付平台可能多次推送同一个 webhook。应为支付交易 ID 设置唯一约束,并在调用 Zoho Books 前检查是否已经创建过对应的 Customer Payment。
不要只相信 webhook 内容
必须验证 webhook 签名,并通过支付服务的服务端接口重新查询交易。至少要核对以下信息:
- 支付状态;
- 商户订单号;
- 金额;
- 币种;
- 收款账户;
- 是否已经退款或撤销。
处理手续费和部分付款
Customer Payment 中应用到发票的金额,通常应是客户实际偿还的应收金额,而不是扣除网关手续费后的银行入账净额。手续费的记账方式需要按照组织的会计科目和对账规则处理。
如果支持分期或部分付款,amount_applied 只能填写本次实际应用到该发票的金额。
保护发票链接
发票中可能包含姓名、地址、税号和交易明细。短信里的下载地址应使用 HTTPS,并设置有效期,同时避免使用可预测的连续编号。必要时,还应增加一次性验证码或身份校验。
核实当前接口能力
Zoho Books 的在线支付能力会因数据中心、地区、订阅方案和已连接的支付网关而异。如果创建或查询发票的响应中包含官方托管付款页面 URL,可以直接通过 SMS 服务发送该 URL。如果响应中没有这个地址,就需要使用外部支付服务生成链接。无论采用哪种方式,SMS 都应由独立的短信服务发送。
备注:内容仅供参考。
