You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Firebase云函数中处理IPN及订单支付后数据更新问询

刚好之前做过基于Firebase的支付集成流程,给你分享一套完整的实现方案,涵盖从客户端发起结账到支付回调更新订单的全流程:

整体流程概述
  1. 客户端用户确认待购订单后,点击「结账」调用Firebase可调用云函数
  2. 云函数拉取订单详情,调用支付服务商API生成在线支付链接并返回给客户端
  3. 用户跳转至支付页面完成交易,支付服务商触发Webhook请求到指定的Firebase云函数
  4. 云函数验证支付合法性后,更新对应订单的支付状态、交易流水号等信息到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:28:41