如何为 Parse.com 构造支付成功后的 callback URL?
结论
不要把 Parse 的 App ID、REST API Key 拼进支付回调 URL,Master Key 更不能这样处理。
如果支付服务商无法添加 Parse REST API 要求的请求头,就需要增加一个公开的服务端回调端点。这个端点负责接收支付通知、验证支付平台签名,再通过服务端 Parse SDK 或受保护的 REST 请求更新订单。
配置前还要分清支付平台提供的是哪一种 URL:
- Success URL:支付完成后,用户浏览器会跳转到这个地址。它只适合展示支付结果。
- Webhook / Notify URL:支付平台的服务器会主动向这个地址发送支付结果,适合用来修改订单状态。
如果平台只提供 Success URL,不能仅凭 URL 中的 orderId 或 status=success 判断付款成功,因为用户可以伪造这些参数。
推荐流程
完整流程如下:
- 创建订单时,生成不可预测的订单编号,并在 Parse 中将订单保存为“待支付”。
- 将一个公开的 HTTPS 地址配置为支付平台的 Webhook 或 Notify URL。
- 由回调端点读取支付平台发送的参数。
- 按照支付平台规定的方法验证签名。必要时,再调用平台的订单查询 API 核对交易。
- 检查金额、币种、商户号和订单编号。
- 通过 Parse Server SDK 或 Master Key 在服务端更新订单。
- 返回支付平台要求的确认响应,避免平台重复通知。
- Success URL 只用于向用户展示支付结果。页面应从后端重新查询订单状态。
回调 URL 的形式
回调 URL 通常只需要包含端点地址:
https://api.example.com/webhooks/payment-provider
如果需要区分环境,可以通过不敏感的路径来区分:
https://api.example.com/webhooks/payment-provider/production
不要把 URL 构造成这样:
https://parse.example.com/classes/Order/123
?appId=YOUR_APP_ID
&restApiKey=YOUR_REST_API_KEY
即使某个接口支持从查询参数读取凭据,也不应该在支付回调中这样使用。URL 可能出现在支付平台记录、代理日志、访问日志、浏览器历史和监控系统里,比请求头中的内容更容易泄露。
Node.js 回调示例
下面的代码展示了推荐的结构。签名算法和字段名称必须按照支付平台的文档替换,不能直接套用示例中的 HMAC 计算方式。
import express from "express";
import crypto from "node:crypto";
import Parse from "parse/node.js";
const app = express();
// 必须根据支付平台实际发送的 Content-Type 配置。
// 有些平台验签要求使用未经解析的原始请求体。
app.use(
"/webhooks/payment-provider",
express.raw({ type: "application/json" })
);
Parse.initialize(process.env.PARSE_APP_ID);
Parse.serverURL = process.env.PARSE_SERVER_URL;
Parse.masterKey = process.env.PARSE_MASTER_KEY;
function verifySignature(rawBody, signature) {
const expected = crypto
.createHmac("sha256", process.env.PAYMENT_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
if (
typeof signature !== "string" ||
signature.length !== expected.length
) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post("/webhooks/payment-provider", async (req, res) => {
try {
const signature = req.get("X-Payment-Signature");
if (!verifySignature(req.body, signature)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
if (event.type !== "payment.succeeded") {
return res.status(200).send("ignored");
}
const orderId = event.data.orderId;
const transactionId = event.data.transactionId;
const paidAmount = event.data.amount;
const currency = event.data.currency;
const query = new Parse.Query("Order");
const order = await query.get(orderId, { useMasterKey: true });
// 必须根据本地订单核对金额和币种,不能只相信回调中的状态。
if (
order.get("amount") !== paidAmount ||
order.get("currency") !== currency
) {
return res.status(400).send("payment data mismatch");
}
// 支付平台可能重复发送同一个事件,因此处理必须具备幂等性。
if (order.get("transactionId") === transactionId) {
return res.status(200).send("ok");
}
if (order.get("status") !== "pending") {
return res.status(409).send("invalid order state");
}
order.set("status", "paid");
order.set("transactionId", transactionId);
order.set("paidAt", new Date(event.data.paidAt));
await order.save(null, { useMasterKey: true });
return res.status(200).send("ok");
} catch (error) {
console.error("Payment callback failed:", error);
return res.status(500).send("temporary failure");
}
});
app.listen(3000);
如果支付平台发送的是 application/x-www-form-urlencoded,或者签名覆盖原始查询字符串,就要相应调整请求体解析和验签方式。具体实现必须严格遵循该平台的签名文档。
为什么不能直接调用 Parse REST API
Parse REST API 通常要求通过请求头传递应用标识和密钥,例如:
X-Parse-Application-Id: YOUR_APP_ID
X-Parse-REST-API-Key: YOUR_REST_API_KEY
只支持配置固定 URL 的支付服务无法生成这些请求头,也未必能按照 Parse 要求的格式发送 POST 或 PUT 请求。因此,把支付平台的 Success URL 直接指向 Parse REST API,并不是可靠的集成方案。
App ID 或某些客户端 Key 即使不属于最高级别的秘密,也不能用来证明请求来自支付平台。判断请求是否可信,应依据支付平台的 webhook 签名、共享密钥、证书验证,或者服务器之间的交易查询结果。
Master Key 可以绕过 Class-Level Permissions 和 ACL,必须只保存在可信的服务端,绝不能写进 URL、客户端代码或公开仓库。
如果使用的是旧 Parse.com
托管版 Parse.com 已经停止服务。旧答案中涉及 parse.com、parseapp.com 或旧 Cloud Code 路由的做法,不一定适用于现在的 Parse Server。
如果项目使用的是迁移后的 Parse Server,可以采用以下方式:
- 在现有 Node.js/Express 应用中增加 webhook 路由;
- 单独部署一个轻量的 serverless function;
- 使用支付平台官方支持的 webhook 接收服务,再从服务端调用 Parse;
- 如果当前 Parse Server 部署支持自定义 Cloud Code HTTP 路由,也可以在那里接收回调。具体写法取决于部署方式和版本。
无论采用哪种方式,公开端点都只负责接收并验证支付通知。Parse 凭据和订单更新操作必须留在服务端。
需要特别注意
- 不要把 Success URL 当作可信的支付通知。
- 不要根据 URL 中的
success=true直接发货或开通服务。 - 回调地址必须使用 HTTPS。
- 验证签名和时间戳。如果平台支持,还要阻止重放攻击。
- 核对订单号、金额、币种和商户身份。
- 按交易 ID 或事件 ID 进行幂等处理。
- 保存回调所需的审计记录,但不要记录完整密钥或敏感支付数据。
- 按照支付平台的要求返回正确的状态码或响应文本,否则平台可能持续重试。
- 浏览器返回页面应向服务端查询订单状态,因为 webhook 和浏览器跳转的到达顺序并不固定。
备注:内容仅供参考。
