在Firebase云函数中处理IPN及订单支付后数据更新问询
刚好之前做过基于Firebase的支付集成流程,给你分享一套完整的实现方案,涵盖从客户端发起结账到支付回调更新订单的全流程:
整体流程概述
- 客户端用户确认待购订单后,点击「结账」调用Firebase可调用云函数
- 云函数拉取订单详情,调用支付服务商API生成在线支付链接并返回给客户端
- 用户跳转至支付页面完成交易,支付服务商触发Webhook请求到指定的Firebase云函数
- 云函数验证支付合法性后,更新对应订单的支付状态、交易流水号等信息到Firestore
1. 前置准备工作
- Firebase配置:确保已创建Firebase项目,启用Cloud Functions和Firestore服务
- 支付服务商对接:以Stripe为例(其他服务商如PayPal逻辑类似),注册账号并获取
Secret Key和Webhook Secret - Firestore订单结构设计:建议订单集合
orders的文档字段包含:orderId: 唯一订单ID(自动生成或自定义)userId: 下单用户ID(关联Firebase Auth)items: 商品列表数组(含商品ID、名称、单价、数量)totalAmount: 订单总金额(单位:元)status: 订单状态(pending待支付 /paid已支付 /failed支付失败)paymentDetails: 支付详情对象(支付成功后填充,含交易流水号、支付方式、支付时间等)
2. 客户端发起结账请求
客户端点击「结账」后,调用Firebase可调用云函数,传入目标订单ID(确保订单已提前存入Firestore):
// 客户端代码(以React为例,原生JS/Vue逻辑一致) import { getFunctions, httpsCallable } from "firebase/functions"; const handleCheckout = async (orderId) => { try { const functions = getFunctions(); const createPaymentLink = httpsCallable(functions, "createPaymentLink"); const result = await createPaymentLink({ orderId }); // 跳转到支付服务商的支付页面 window.location.href = result.data.paymentUrl; } catch (error) { console.error("结账请求失败:", error); // 给用户显示友好错误提示,比如"订单异常,请稍后重试" } };
3. 云函数生成支付链接
编写Firebase云函数,拉取订单详情后调用支付服务商API生成支付链接,这里以Stripe的Checkout Session为例:
// Firebase Cloud Functions代码 const functions = require("firebase-functions"); const admin = require("firebase-admin"); const stripe = require("stripe")(functions.config().stripe.secret_key); admin.initializeApp(); exports.createPaymentLink = functions.https.onCall(async (data, context) => { // 验证用户身份,确保只有订单所属用户能发起支付 if (!context.auth) { throw new functions.https.HttpsError( "unauthenticated", "请先登录再进行支付" ); } const { orderId } = data; // 从Firestore获取订单信息 const orderDoc = await admin.firestore().collection("orders").doc(orderId).get(); if (!orderDoc.exists) { throw new functions.https.HttpsError("not-found", "订单不存在"); } const orderData = orderDoc.data(); // 校验订单状态,防止重复支付 if (orderData.status !== "pending") { throw new functions.https.HttpsError( "failed-precondition", "该订单当前状态无法支付" ); } // 创建Stripe Checkout Session const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: [ { price_data: { currency: "cny", // 根据业务调整币种 product_data: { name: `订单支付:${orderId}` }, unit_amount: orderData.totalAmount * 100, // Stripe以"分"为单位,需转换 }, quantity: 1, }, ], mode: "payment", success_url: `${process.env.CLIENT_URL}/payment-success?orderId=${orderId}`, // 支付成功跳转页 cancel_url: `${process.env.CLIENT_URL}/payment-cancel?orderId=${orderId}`, // 支付取消跳转页 metadata: { orderId }, // 存入订单ID,方便回调时识别 }); return { paymentUrl: session.url }; });
4. 支付回调处理(核心步骤)
支付服务商在用户完成支付后,会向指定的Webhook地址发送请求,我们需要编写云函数验证请求合法性并更新订单:
// Firebase Cloud Functions:处理Stripe支付回调 exports.stripeWebhook = functions.https.onRequest(async (req, res) => { const sig = req.headers["stripe-signature"]; let event; try { // 验证Webhook签名,防止伪造请求 event = stripe.webhooks.constructEvent( req.rawBody, sig, functions.config().stripe.webhook_secret ); } catch (err) { functions.logger.error("Webhook签名验证失败:", err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } // 处理支付成功事件 if (event.type === "checkout.session.completed") { const session = event.data.object; const orderId = session.metadata.orderId; // 幂等性处理:先查询订单当前状态,避免重复更新 const orderDoc = await admin.firestore().collection("orders").doc(orderId).get(); if (orderDoc.data().status === "paid") { functions.logger.info(`订单 ${orderId} 已处理过支付成功事件,跳过`); return res.json({ received: true }); } // 更新订单状态和支付详情 await admin.firestore().collection("orders").doc(orderId).update({ status: "paid", paymentDetails: { transactionId: session.payment_intent, paymentMethod: session.payment_method_types[0], paidAt: admin.firestore.Timestamp.now(), amount: session.amount_total / 100, // 转换回元单位 }, }); functions.logger.info(`订单 ${orderId} 支付成功,已更新状态`); } // 处理支付失败事件 if (event.type === "payment_intent.payment_failed") { const paymentIntent = event.data.object; const orderId = paymentIntent.metadata.orderId; await admin.firestore().collection("orders").doc(orderId).update({ status: "failed", paymentDetails: { failureReason: paymentIntent.last_payment_error?.message, failedAt: admin.firestore.Timestamp.now(), }, }); functions.logger.warn(`订单 ${orderId} 支付失败: ${paymentIntent.last_payment_error?.message}`); } res.json({ received: true }); });
5. 客户端支付结果展示
用户支付完成后会跳转回客户端页面,此时可以查询订单状态并展示结果:
// 客户端支付成功页面代码(React) import { useEffect, useState } from "react"; import { getFirestore, doc, getDoc } from "firebase/firestore"; const PaymentSuccessPage = () => { const [order, setOrder] = useState(null); const orderId = new URLSearchParams(window.location.search).get("orderId"); useEffect(() => { if (!orderId) return; const fetchOrderStatus = async () => { const db = getFirestore(); const orderDoc = await getDoc(doc(db, "orders", orderId)); if (orderDoc.exists()) { setOrder(orderDoc.data()); } }; fetchOrderStatus(); }, [orderId]); if (!order) return <div>正在查询支付结果...</div>; return ( <div className="payment-result"> {order.status === "paid" ? ( <> <h2>支付成功🎉</h2> <p>你的订单已确认,交易流水号:{order.paymentDetails.transactionId}</p> </> ) : order.status === "failed" ? ( <> <h2>支付失败💔</h2> <p>失败原因:{order.paymentDetails.failureReason}</p> <button onClick={() => handleCheckout(orderId)}>重新支付</button> </> ) : ( <div>支付状态处理中,请稍后...</div> )} </div> ); }; export default PaymentSuccessPage;
关键注意事项
- 身份验证:客户端调用可调用云函数时,Firebase会自动携带用户身份信息,云函数中一定要验证
context.auth,防止恶意用户发起他人订单的支付请求 - Webhook签名验证:必须开启并验证签名,这是防止伪造支付回调的关键,绝对不能省略
- 幂等性:支付服务商可能重复发送回调事件,一定要先检查订单当前状态,避免重复更新
- 环境变量管理:支付服务商的密钥不要硬编码,使用Firebase环境变量存储:
firebase functions:config:set stripe.secret_key="你的密钥" stripe.webhook_secret="你的Webhook密钥" - 错误日志:云函数中要添加详细的日志记录,方便后续排查支付异常问题
内容的提问来源于stack exchange,提问作者Eliya Cohen
相关产品推荐
相关产品推荐

