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
相关产品推荐
相关产品推荐

