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

Stripe支付成功重定向后Blob URL无法访问的解决咨询

Blob URL在Stripe支付跳转后失效的解决方案

问题场景

我在Next.js项目中使用Stripe处理支付,跳转成功页时通过URL参数传递Blob格式的图片地址,但在成功页尝试fetch这个Blob URL时,出现错误:

GET blob:http://localhost:3000/2a47a926-be04-49a9-ad96-3279c540ebb4 net::ERR_FILE_NOT_FOUND

原Stripe跳转代码:

import { loadStripe } from "@stripe/stripe-js";

export async function Stripe({ lineItems }, imageUrls) {
  let stripePromise = null;

  const getStripe = () => {
    if (!stripePromise) {
      stripePromise = loadStripe(process.env.NEXT_PUBLIC_API_KEY);
    }
    return stripePromise;
  };

  const stripe = await getStripe();

  await stripe.redirectToCheckout({
    mode: "payment",
    lineItems,
    successUrl: `http://localhost:3000/success?pdf=${imageUrls}`,
    cancelUrl: window.location.origin,
  });
}

原成功页代码:

import Layout from "../components/Layout/Layout";
import { useEffect } from "react";

function SuccessPage() {
  useEffect(() => {
    const params = new Proxy(new URLSearchParams(window.location.search), {
      get: (searchParams, prop) => searchParams.get(prop),
    });
    let value = params.pdf;
    console.log("this is value");
    console.log(value);

    async function getFileFromUrl(imageUrl) {
      const fileResponse = await fetch(imageUrl);
      const contentType = fileResponse.headers.get("content-type");
      const blob = await fileResponse.blob();
      const ditheredImageFile = new File([blob], `test.png`, {
        contentType,
      });
      return ditheredImageFile;
    }
    let imageFile = getFileFromUrl(value);
    console.log("this is imageFile");
    console.log(imageFile);
  }, []);

  return (
    <Layout>
      <h3>Thank You For Your Order!</h3>
    </Layout>
  );
}

export default SuccessPage;

问题原因

Blob URL是浏览器在当前会话生成的临时资源地址,一旦页面跳转(比如Stripe的redirectToCheckout),原会话的Blob资源会被浏览器释放,新页面无法访问这个临时URL,所以会触发文件找不到的错误。

两种可行解决方案

方案1:将Blob转为Base64字符串传递

适合小体积图片,直接把Blob编码成Base64字符串,通过URL参数传递到成功页,再解码还原成文件。

修改Stripe跳转代码

import { loadStripe } from "@stripe/stripe-js";

// 新增:Blob转Base64工具函数
async function blobToBase64(blobUrl) {
  const response = await fetch(blobUrl);
  const blob = await response.blob();
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  });
}

export async function Stripe({ lineItems }, imageUrls) {
  let stripePromise = null;

  const getStripe = () => {
    if (!stripePromise) {
      stripePromise = loadStripe(process.env.NEXT_PUBLIC_API_KEY);
    }
    return stripePromise;
  };

  // 把所有Blob URL转成Base64并编码,避免URL解析问题
  const base64Images = await Promise.all(imageUrls.map(blobToBase64));
  const encodedImages = encodeURIComponent(JSON.stringify(base64Images));

  const stripe = await getStripe();

  await stripe.redirectToCheckout({
    mode: "payment",
    lineItems,
    successUrl: `http://localhost:3000/success?pdf=${encodedImages}`,
    cancelUrl: window.location.origin,
  });
}

修改成功页代码

import Layout from "../components/Layout/Layout";
import { useEffect } from "react";

function SuccessPage() {
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const encodedImages = params.get("pdf");
    if (!encodedImages) return;

    // 解码并解析Base64数组
    const base64Images = JSON.parse(decodeURIComponent(encodedImages));

    async function getFileFromBase64(base64Str) {
      // 拆分Base64的类型和数据部分
      const [typePart, dataPart] = base64Str.split(",");
      const contentType = typePart.match(/:(.*?);/)[1];
      // 将Base64转为Blob
      const byteCharacters = atob(dataPart);
      const byteArrays = [];
      for (let i = 0; i < byteCharacters.length; i++) {
        byteArrays.push(byteCharacters.charCodeAt(i));
      }
      const blob = new Blob([new Uint8Array(byteArrays)], { contentType });
      // 转成File对象
      return new File([blob], `test.png`, { contentType });
    }

    // 处理每张图片
    base64Images.forEach(async (base64) => {
      const imageFile = await getFileFromBase64(base64);
      console.log("生成的图片文件:", imageFile);
      // 这里可以添加下载、展示等后续操作
    });
  }, []);

  return (
    <Layout>
      <h3>感谢您的下单!</h3>
    </Layout>
  );
}

export default SuccessPage;

方案2:上传Blob到服务器,传递永久地址

适合大体积图片,避免Base64导致URL过长的问题。先把Blob上传到自己的服务器或云存储,拿到永久访问地址后再传递到成功页。

步骤1:添加上传到服务器的函数

在Stripe代码中新增上传逻辑:

// 新增:上传Blob到服务器API
async function uploadBlobToServer(blob) {
  const formData = new FormData();
  formData.append("image", blob, "temp-image.png");
  const response = await fetch("/api/upload-image", {
    method: "POST",
    body: formData,
  });
  const data = await response.json();
  return data.imageUrl; // 服务器返回的图片访问地址
}

步骤2:修改Stripe跳转逻辑

// 在Stripe函数中替换原imageUrls处理部分
const serverImageUrls = await Promise.all(
  imageUrls.map(async (blobUrl) => {
    const response = await fetch(blobUrl);
    const blob = await response.blob();
    return uploadBlobToServer(blob);
  })
);
const encodedImages = encodeURIComponent(JSON.stringify(serverImageUrls));

// 后续跳转代码不变,successUrl使用encodedImages

步骤3:创建Next.js API路由处理上传

新建pages/api/upload-image.js:

import fs from "fs";
import path from "path";
import formidable from "formidable";

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  if (req.method !== "POST") {
    return res.status(405).json({ message: "仅支持POST请求" });
  }

  const form = new formidable.IncomingForm();
  form.parse(req, async (err, fields, files) => {
    if (err) {
      return res.status(500).json({ message: "上传失败" });
    }

    const file = files.image;
    // 生产环境建议使用云存储,比如AWS S3、阿里云OSS
    const uploadDir = path.join(process.cwd(), "public", "uploads");
    fs.mkdirSync(uploadDir, { recursive: true });
    const targetPath = path.join(uploadDir, file.name);

    // 移动上传的文件到目标目录
    fs.renameSync(file.filepath, targetPath);

    // 返回可访问的图片URL
    const imageUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/uploads/${file.name}`;
    res.status(200).json({ imageUrl });
  });
}

注意:需要先安装formidable包:npm install formidable

步骤4:修改成功页代码

直接使用服务器地址fetch即可:

// success.js中的getFileFromUrl可以复用原逻辑
async function getFileFromUrl(imageUrl) {
  const fileResponse = await fetch(imageUrl);
  const contentType = fileResponse.headers.get("content-type");
  const blob = await fileResponse.blob();
  return new File([blob], `test.png`, { contentType });
}

// 解析参数部分修改为:
const encodedImages = params.get("pdf");
if (!encodedImages) return;
const serverImageUrls = JSON.parse(decodeURIComponent(encodedImages));

serverImageUrls.forEach(async (url) => {
  const imageFile = await getFileFromUrl(url);
  console.log("生成的图片文件:", imageFile);
});

注意事项

  • 方案1的Base64编码会比原Blob体积大30%左右,图片过大可能导致URL超过浏览器长度限制(一般浏览器支持的URL长度在2KB-8KB之间)。
  • 方案2需要服务器或云存储支持,生产环境要注意临时文件的清理(比如定时删除服务器上的上传文件),避免占用存储空间。

内容的提问来源于stack exchange,提问作者mtcrts70

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:01:30