forked from 77media/video-flow
支付初步对接,等会接口
This commit is contained in:
parent
1b80180102
commit
caa2bfb5e4
@ -1,26 +1,53 @@
|
||||
'use client'
|
||||
import './globals.css';
|
||||
import type { Metadata } from 'next';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { Providers } from '@/components/providers';
|
||||
import { ConfigProvider, theme } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import { createScreenAdapter } from '@/utils/tools';
|
||||
import { ScreenAdapter } from './ScreenAdapter';
|
||||
import CallbackModal from '@/components/common/CallbackModal';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'AI Movie Flow - Create Amazing Videos with AI',
|
||||
description: 'Professional AI-powered video creation platform with advanced editing tools',
|
||||
};
|
||||
// 创建上下文来传递弹窗控制方法
|
||||
const CallbackModalContext = createContext<{
|
||||
setShowCallbackModal: (show: boolean) => void
|
||||
} | null>(null)
|
||||
|
||||
// Hook 来使用弹窗控制方法
|
||||
export const useCallbackModal = () => {
|
||||
const context = useContext(CallbackModalContext)
|
||||
if (!context) {
|
||||
throw new Error('useCallbackModal must be used within CallbackModalProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [showCallbackModal, setShowCallbackModal] = useState(false)
|
||||
const openCallback = async function (ev: MessageEvent<any>) {
|
||||
console.log(ev)
|
||||
if (ev.data.type === 'waiting-payment') {
|
||||
setShowCallbackModal(true)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', openCallback)
|
||||
return () => {
|
||||
window.removeEventListener('message', openCallback)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<meta name="robots" content="noindex"></meta>
|
||||
<head>
|
||||
<title>AI Movie Flow - Create Amazing Videos with AI</title>
|
||||
<meta name="description" content="Professional AI-powered video creation platform with advanced editing tools" />
|
||||
<meta name="robots" content="noindex" />
|
||||
</head>
|
||||
<body className="font-sans antialiased">
|
||||
<ConfigProvider
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: theme.darkAlgorithm,
|
||||
token: {
|
||||
@ -32,14 +59,17 @@ export default function RootLayout({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Providers>
|
||||
{/* <ScreenAdapter /> */}
|
||||
<div id="app" className='h-full w-full'>
|
||||
{children}
|
||||
</div>
|
||||
</Providers>
|
||||
<CallbackModalContext.Provider value={{ setShowCallbackModal }}>
|
||||
<Providers>
|
||||
{/* <ScreenAdapter /> */}
|
||||
<div id="app" className='h-full w-full'>
|
||||
{children}
|
||||
{showCallbackModal && <CallbackModal onClose={() => setShowCallbackModal(false)} />}
|
||||
</div>
|
||||
</Providers>
|
||||
</CallbackModalContext.Provider>
|
||||
</ConfigProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,197 +1,21 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Result, Button, Spin, Card } from 'antd'
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { getCheckoutSessionStatus, PaymentStatusResponse } from '@/lib/stripe'
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* 支付状态枚举
|
||||
*/
|
||||
enum PaymentStatus {
|
||||
LOADING = 'loading',
|
||||
SUCCESS = 'success',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付回调页面组件
|
||||
* 处理Stripe Checkout支付完成后的状态展示和用户操作
|
||||
*/
|
||||
export default function PayCallbackPage() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
|
||||
const [paymentInfo, setPaymentInfo] = useState<any>(null)
|
||||
const callBackUrl = localStorage.getItem('callBackUrl') || '/'
|
||||
|
||||
/**
|
||||
* 获取Stripe Checkout Session支付状态
|
||||
*/
|
||||
const fetchPaymentStatus = async () => {
|
||||
try {
|
||||
// 从URL参数获取session_id和user_id
|
||||
const sessionId = searchParams.get('session_id')
|
||||
const userId = searchParams.get('user_id')
|
||||
|
||||
if (!sessionId || !userId) {
|
||||
throw new Error('缺少必要的参数: session_id 或 user_id')
|
||||
}
|
||||
|
||||
// 调用真实的Stripe API获取支付状态
|
||||
const response = await getCheckoutSessionStatus(sessionId, userId)
|
||||
|
||||
if (response.successful && response.data) {
|
||||
const { payment_status, biz_order_no, pay_time, subscription } = response.data
|
||||
|
||||
if (payment_status === 'success') {
|
||||
setPaymentStatus(PaymentStatus.SUCCESS)
|
||||
setPaymentInfo({
|
||||
orderId: biz_order_no,
|
||||
sessionId,
|
||||
paymentTime: pay_time,
|
||||
subscription: subscription ? {
|
||||
planName: subscription.plan_name,
|
||||
planDisplayName: subscription.plan_display_name,
|
||||
status: subscription.status,
|
||||
currentPeriodEnd: subscription.current_period_end
|
||||
} : null
|
||||
})
|
||||
} else if (payment_status === 'fail') {
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
setPaymentInfo({
|
||||
orderId: biz_order_no,
|
||||
sessionId,
|
||||
errorMessage: '支付处理失败,请重试'
|
||||
})
|
||||
} else {
|
||||
// pending状态,继续等待
|
||||
setTimeout(fetchPaymentStatus, 2000)
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || '获取支付状态失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取支付状态失败:', error)
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
setPaymentInfo({
|
||||
errorMessage: error instanceof Error ? error.message : '网络错误,无法获取支付状态'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页的函数
|
||||
*/
|
||||
const handleGoBack = () => {
|
||||
router.push(callBackUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新支付的函数
|
||||
*/
|
||||
const handleRetryPayment = () => {
|
||||
router.push('/pricing')
|
||||
}
|
||||
export default function payCallback() {
|
||||
const searchParams = useSearchParams();
|
||||
const sessionId = searchParams.get("session_id");
|
||||
const userId = searchParams.get("user_id");
|
||||
const canceled = searchParams.get("canceled")||false;
|
||||
|
||||
useEffect(() => {
|
||||
fetchPaymentStatus()
|
||||
}, [])
|
||||
window.opener.postMessage(
|
||||
{ type: "payment-callback", canceled, sessionId, userId },
|
||||
"*"
|
||||
);
|
||||
window.close();
|
||||
}, []);
|
||||
|
||||
// 加载状态
|
||||
if (paymentStatus === PaymentStatus.LOADING) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-black">
|
||||
<div className="w-96 text-center bg-black border border-white/20 rounded-lg p-8">
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 48, color: 'white' }} spin />}
|
||||
size="large"
|
||||
/>
|
||||
<div className="mt-6 text-lg text-white">正在获取支付状态...</div>
|
||||
<div className="mt-2 text-sm text-white/70">请稍候,我们正在处理您的支付信息</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 支付成功状态
|
||||
if (paymentStatus === PaymentStatus.SUCCESS) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-black">
|
||||
<div className="w-[32rem] text-center bg-black border border-white/20 rounded-lg p-8">
|
||||
<div className="mb-6">
|
||||
<CheckCircleOutlined className="text-white text-6xl mb-4" />
|
||||
<h1 className="text-white text-2xl font-bold mb-2">支付成功!</h1>
|
||||
<p className="text-white/70 text-base">{`订单号: ${paymentInfo?.orderId || 'N/A'}`}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center text-white/70 mb-8 space-y-2">
|
||||
{paymentInfo?.subscription && (
|
||||
<>
|
||||
<p>订阅计划: {paymentInfo.subscription.planDisplayName}</p>
|
||||
<p>订阅状态: {paymentInfo.subscription.status}</p>
|
||||
{paymentInfo.subscription.currentPeriodEnd && (
|
||||
<p>到期时间: {new Date(paymentInfo.subscription.currentPeriodEnd).toLocaleString()}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{paymentInfo?.paymentTime && (
|
||||
<p>支付时间: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
|
||||
)}
|
||||
<p>感谢您的购买!</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleGoBack}
|
||||
data-alt="back-to-previous-page-button"
|
||||
className="w-full h-12 bg-white text-black hover:bg-white/90 border border-white/20"
|
||||
>
|
||||
返回上一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 支付失败状态
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-black">
|
||||
<div className="w-[32rem] text-center bg-black border border-white/20 rounded-lg p-8">
|
||||
<div className="mb-6">
|
||||
<CloseCircleOutlined className="text-white text-6xl mb-4" />
|
||||
<h1 className="text-white text-2xl font-bold mb-2">支付失败</h1>
|
||||
<p className="text-white/70 text-base">{paymentInfo?.errorMessage || '支付处理过程中发生错误'}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center text-white/70 mb-8 space-y-2">
|
||||
<p>订单号: {paymentInfo?.orderId || 'N/A'}</p>
|
||||
<p>如果问题持续存在,请联系客服</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleRetryPayment}
|
||||
data-alt="retry-payment-button"
|
||||
className="w-full h-12 bg-white text-black hover:bg-white/90 border border-white/20"
|
||||
>
|
||||
重新支付
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="large"
|
||||
onClick={handleGoBack}
|
||||
data-alt="back-to-previous-page-button"
|
||||
className="w-full h-12 bg-black text-white hover:bg-white hover:text-black border border-white/20"
|
||||
>
|
||||
返回上一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Check, ArrowLeft } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { fetchSubscriptionPlans, SubscriptionPlan } from '@/lib/stripe';
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Check, ArrowLeft } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { fetchSubscriptionPlans, SubscriptionPlan } from "@/lib/stripe";
|
||||
|
||||
export default function PricingPage() {
|
||||
|
||||
@ -18,9 +24,7 @@ export default function PricingPage() {
|
||||
}
|
||||
/**价格方案 */
|
||||
function HomeModule5() {
|
||||
const [billingType, setBillingType] = useState<'month' | 'year'>(
|
||||
"month"
|
||||
);
|
||||
const [billingType, setBillingType] = useState<"month" | "year">("month");
|
||||
|
||||
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
||||
|
||||
@ -31,24 +35,29 @@ function HomeModule5() {
|
||||
const plansData = await fetchSubscriptionPlans();
|
||||
setPlans(plansData);
|
||||
} catch (err) {
|
||||
console.error('加载订阅计划失败:', err);
|
||||
console.error("加载订阅计划失败:", err);
|
||||
}
|
||||
};
|
||||
|
||||
loadPlans();
|
||||
}, []);
|
||||
|
||||
const pricingPlans = useMemo<{
|
||||
title: string;
|
||||
price: number;
|
||||
credits: string;
|
||||
buttonText: string;
|
||||
features: string[];
|
||||
}[]>(() => {
|
||||
const pricingPlans = useMemo<
|
||||
{
|
||||
title: string;
|
||||
price: number;
|
||||
credits: string;
|
||||
buttonText: string;
|
||||
features: string[];
|
||||
}[]
|
||||
>(() => {
|
||||
return plans.map((plan) => {
|
||||
return {
|
||||
title: plan.display_name || plan.name,
|
||||
price: billingType === "month" ? plan.price_month/100 : plan.price_year/100,
|
||||
price:
|
||||
billingType === "month"
|
||||
? plan.price_month / 100
|
||||
: plan.price_year / 100,
|
||||
credits: plan.description,
|
||||
buttonText: plan.is_free ? "Try For Free" : "Subscribe Now",
|
||||
features: plan.features || [],
|
||||
@ -57,13 +66,15 @@ function HomeModule5() {
|
||||
}, [plans, billingType]);
|
||||
|
||||
const handleSubscribe = async (planName: string) => {
|
||||
if (planName === 'hobby') {
|
||||
if (planName === "hobby") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用新的Checkout Session方案(更简单!)
|
||||
const { createCheckoutSession, redirectToCheckout } = await import('@/lib/stripe');
|
||||
const { createCheckoutSession, redirectToCheckout } = await import(
|
||||
"@/lib/stripe"
|
||||
);
|
||||
|
||||
// 从localStorage获取当前用户信息
|
||||
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
||||
@ -76,16 +87,15 @@ function HomeModule5() {
|
||||
const result = await createCheckoutSession({
|
||||
user_id: String(User.id),
|
||||
plan_name: planName,
|
||||
billing_cycle: billingType
|
||||
billing_cycle: billingType,
|
||||
});
|
||||
|
||||
if (!result.successful || !result.data) {
|
||||
throw new Error("create checkout session failed");
|
||||
}
|
||||
|
||||
window.opener.postMessage({ type: "waiting-payment" }, "*");
|
||||
// 2. 直接跳转到Stripe托管页面(就这么简单!)
|
||||
redirectToCheckout(result.data.checkout_url);
|
||||
|
||||
window.location.href = result.data.checkout_url;
|
||||
} catch (error) {
|
||||
throw new Error("create checkout session failed, please try again later");
|
||||
}
|
||||
@ -147,7 +157,10 @@ function HomeModule5() {
|
||||
<p className="text-white text-[0.875rem] mb-[1rem]">
|
||||
{plan.credits}
|
||||
</p>
|
||||
<button onClick={() => handleSubscribe(plan.title)} className="w-full bg-white text-black py-[0.75rem] rounded-full mb-[1rem] hover:bg-black hover:text-white transition-colors border border-white/20">
|
||||
<button
|
||||
onClick={() => handleSubscribe(plan.title)}
|
||||
className="w-full bg-white text-black py-[0.75rem] rounded-full mb-[1rem] hover:bg-black hover:text-white transition-colors border border-white/20"
|
||||
>
|
||||
{plan.buttonText}
|
||||
</button>
|
||||
<p className="w-full text-center text-white/60 text-[0.75rem] mb-[2rem]">
|
||||
|
||||
205
components/common/CallbackModal.tsx
Normal file
205
components/common/CallbackModal.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
|
||||
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Result, Button, Spin, Card, Modal } from 'antd'
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { getCheckoutSessionStatus, PaymentStatusResponse } from '@/lib/stripe'
|
||||
|
||||
/**
|
||||
* 支付状态枚举
|
||||
*/
|
||||
enum PaymentStatus {
|
||||
LOADING = 'loading',
|
||||
SUCCESS = 'success',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付回调模态框组件
|
||||
* 处理Stripe Checkout支付完成后的状态展示和用户操作
|
||||
*/
|
||||
export default function CallbackModal({ onClose }: { onClose: () => void }) {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
|
||||
const [paymentInfo, setPaymentInfo] = useState<any>(null)
|
||||
const [isModalOpen, setIsModalOpen] = useState(true)
|
||||
const callBackUrl = localStorage.getItem('callBackUrl') || '/'
|
||||
|
||||
/**
|
||||
* 获取Stripe Checkout Session支付状态
|
||||
*/
|
||||
const fetchPaymentStatus = async () => {
|
||||
try {
|
||||
// 从URL参数获取session_id和user_id
|
||||
const sessionId = searchParams.get('session_id')
|
||||
const userId = searchParams.get('user_id')
|
||||
|
||||
if (!sessionId || !userId) {
|
||||
throw new Error('Missing required parameters: session_id or user_id')
|
||||
}
|
||||
|
||||
// 调用真实的Stripe API获取支付状态
|
||||
const response = await getCheckoutSessionStatus(sessionId, userId)
|
||||
|
||||
if (response.successful && response.data) {
|
||||
const { payment_status, biz_order_no, pay_time, subscription } = response.data
|
||||
|
||||
if (payment_status === 'success') {
|
||||
setPaymentStatus(PaymentStatus.SUCCESS)
|
||||
setPaymentInfo({
|
||||
orderId: biz_order_no,
|
||||
sessionId,
|
||||
paymentTime: pay_time,
|
||||
subscription: subscription ? {
|
||||
planName: subscription.plan_name,
|
||||
planDisplayName: subscription.plan_display_name,
|
||||
status: subscription.status,
|
||||
currentPeriodEnd: subscription.current_period_end
|
||||
} : null
|
||||
})
|
||||
} else if (payment_status === 'fail') {
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
setPaymentInfo({
|
||||
orderId: biz_order_no,
|
||||
sessionId,
|
||||
errorMessage: 'Payment processing failed, please try again'
|
||||
})
|
||||
} else {
|
||||
// pending状态,继续等待
|
||||
setTimeout(fetchPaymentStatus, 2000)
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to get payment status')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get payment status:', error)
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
setPaymentInfo({
|
||||
errorMessage: error instanceof Error ? error.message : 'Network error, unable to get payment status'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染模态框内容
|
||||
const renderModalContent = () => {
|
||||
// 加载状态
|
||||
if (paymentStatus === PaymentStatus.LOADING) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 48, color: 'white' }} spin />}
|
||||
size="large"
|
||||
/>
|
||||
<div className="mt-6 text-lg text-white">Getting payment status...</div>
|
||||
<div className="mt-2 text-sm text-white/70">Please wait, we are processing your payment information</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 支付成功状态
|
||||
if (paymentStatus === PaymentStatus.SUCCESS) {
|
||||
return (
|
||||
<div className="text-center py-4">
|
||||
<div className="mb-6">
|
||||
<CheckCircleOutlined className="text-white text-6xl mb-4" />
|
||||
<h1 className="text-white text-2xl font-bold mb-2">Payment Successful!</h1>
|
||||
<p className="text-white/70 text-base">{`Order ID: ${paymentInfo?.orderId || 'N/A'}`}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center text-white/70 mb-8 space-y-2">
|
||||
{paymentInfo?.subscription && (
|
||||
<>
|
||||
<p>Subscription Plan: {paymentInfo.subscription.planDisplayName}</p>
|
||||
<p>Subscription Status: {paymentInfo.subscription.status}</p>
|
||||
{paymentInfo.subscription.currentPeriodEnd && (
|
||||
<p>Expiry Date: {new Date(paymentInfo.subscription.currentPeriodEnd).toLocaleString()}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{paymentInfo?.paymentTime && (
|
||||
<p>Payment Time: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
|
||||
)}
|
||||
<p>Thank you for your purchase!</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 支付失败状态
|
||||
return (
|
||||
<div className="text-center py-4">
|
||||
<div className="mb-6">
|
||||
<CloseCircleOutlined className="text-white text-6xl mb-4" />
|
||||
<h1 className="text-white text-2xl font-bold mb-2">Payment Failed</h1>
|
||||
<p className="text-white/70 text-base">{paymentInfo?.errorMessage || 'An error occurred during payment processing'}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center text-white/70 mb-8 space-y-2">
|
||||
<p>Order ID: {paymentInfo?.orderId || 'N/A'}</p>
|
||||
<p>If the problem persists, please contact customer service</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const watResult = async function (ev: MessageEvent<{
|
||||
type: 'payment-callback',
|
||||
canceled: boolean,
|
||||
sessionId: string,
|
||||
userId: string
|
||||
}>) {
|
||||
if (ev.data.type === 'payment-callback') {
|
||||
if (ev.data.canceled) {
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
return
|
||||
}
|
||||
const userId = JSON.parse(localStorage.getItem('currentUser') || '{}').id
|
||||
const res = await getCheckoutSessionStatus(ev.data.sessionId, userId)
|
||||
if (res.successful && res.data) {
|
||||
if(res.data.payment_status === 'success'){
|
||||
setPaymentStatus(PaymentStatus.SUCCESS)
|
||||
}else{
|
||||
setPaymentStatus(PaymentStatus.FAILED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', watResult)
|
||||
return () => {
|
||||
window.removeEventListener('message', watResult)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<Modal
|
||||
open={isModalOpen}
|
||||
closable={true}
|
||||
closeIcon={<CloseCircleOutlined style={{ color: 'white', fontSize: '16px' }} />}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
footer={null}
|
||||
width={500}
|
||||
centered
|
||||
title={null}
|
||||
className="payment-callback-modal"
|
||||
onCancel={onClose}
|
||||
styles={{
|
||||
content: {
|
||||
backgroundColor: 'black',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
},
|
||||
mask: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
},
|
||||
header: {
|
||||
borderBottom: 'none',
|
||||
}
|
||||
}}
|
||||
>
|
||||
{renderModalContent()}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -41,7 +41,7 @@ export function TopBar({
|
||||
);
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const [isLogin, setIsLogin] = useState(false);
|
||||
const pathname = usePathname()
|
||||
const pathname = usePathname();
|
||||
useEffect(() => {
|
||||
const currentUser = localStorage.getItem("currentUser");
|
||||
if (JSON.parse(currentUser || "{}")?.token) {
|
||||
@ -161,7 +161,7 @@ export function TopBar({
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
localStorage.setItem("callBackUrl", pathname);
|
||||
router.push("/pricing");
|
||||
window.open("/pricing", "_blank");
|
||||
}}
|
||||
className="text-gray-300 hover:text-white"
|
||||
>
|
||||
@ -255,7 +255,9 @@ export function TopBar({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-white border-white hover:bg-white/10 rounded-full px-3 py-1 text-[10px]"
|
||||
onClick={() => router.push("/pricing")}
|
||||
onClick={() => {
|
||||
window.open("/pricing", "_blank");
|
||||
}}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
@ -263,7 +265,9 @@ export function TopBar({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-white border-white hover:bg-white/10 rounded-full px-3 py-1 text-[10px]"
|
||||
onClick={() => router.push("/pricing")}
|
||||
onClick={() => {
|
||||
window.open("/pricing", "_blank");
|
||||
}}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
|
||||
@ -27,6 +27,7 @@ import { getResourcesList, Resource } from "@/api/resources";
|
||||
import { Carousel } from "antd";
|
||||
import { TextCanvas } from "../common/TextCanvas";
|
||||
import { fetchSubscriptionPlans, SubscriptionPlan } from "@/lib/stripe";
|
||||
import { useCallbackModal } from "@/app/layout";
|
||||
|
||||
export function HomePage2() {
|
||||
const [hPading, setHPading] = useState(0);
|
||||
@ -407,6 +408,7 @@ function HomeModule5() {
|
||||
const [billingType, setBillingType] = useState<"month" | "year">("month");
|
||||
|
||||
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
||||
const { setShowCallbackModal } = useCallbackModal();
|
||||
const pathname = usePathname();
|
||||
// 从后端获取订阅计划数据
|
||||
useEffect(() => {
|
||||
@ -474,8 +476,8 @@ function HomeModule5() {
|
||||
throw new Error("create checkout session failed");
|
||||
}
|
||||
|
||||
// 2. 直接跳转到Stripe托管页面(就这么简单!)
|
||||
redirectToCheckout(result.data.checkout_url);
|
||||
setShowCallbackModal(true)
|
||||
window.open(result.data.checkout_url, '_blank');
|
||||
} catch (error) {
|
||||
throw new Error("create checkout session failed, please try again later");
|
||||
}
|
||||
|
||||
@ -109,6 +109,6 @@ export async function getCheckoutSessionStatus(
|
||||
*/
|
||||
export function redirectToCheckout(checkoutUrl: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = checkoutUrl;
|
||||
window.open(checkoutUrl, '_blank');
|
||||
}
|
||||
}
|
||||
@ -34,16 +34,20 @@ const nextConfig = {
|
||||
},
|
||||
|
||||
async rewrites() {
|
||||
// 使用环境变量,如果没有则使用默认值
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'https://77.smartvideo.py.qikongjian.com'
|
||||
|
||||
console.log('Environment BASE_URL:', process.env)
|
||||
console.log('Using BASE_URL:', BASE_URL)
|
||||
|
||||
return [
|
||||
{
|
||||
source: '/api/proxy/:path*',
|
||||
destination: BASE_URL+'/:path*',
|
||||
destination: `${BASE_URL}/:path*`,
|
||||
},
|
||||
{
|
||||
source: '/api/resources/:path*',
|
||||
destination: BASE_URL+'/:path*',
|
||||
destination: `${BASE_URL}/:path*`,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user