forked from 77media/video-flow
adds alipay
This commit is contained in:
parent
3022a497d5
commit
f8b2ec2d4b
@ -25,8 +25,10 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [showCallbackModal, setShowCallbackModal] = useState(false)
|
const [showCallbackModal, setShowCallbackModal] = useState(false)
|
||||||
|
const [paymentType, setPaymentType] = useState<'subscription' | 'token'>('subscription')
|
||||||
const openCallback = async function (ev: MessageEvent<any>) {
|
const openCallback = async function (ev: MessageEvent<any>) {
|
||||||
if (ev.data.type === 'waiting-payment') {
|
if (ev.data.type === 'waiting-payment') {
|
||||||
|
setPaymentType(ev.data.paymentType || 'subscription')
|
||||||
setShowCallbackModal(true)
|
setShowCallbackModal(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +86,7 @@ export default function RootLayout({
|
|||||||
{/* <ScreenAdapter /> */}
|
{/* <ScreenAdapter /> */}
|
||||||
<div id="app" className='h-full w-full'>
|
<div id="app" className='h-full w-full'>
|
||||||
{children}
|
{children}
|
||||||
{showCallbackModal && <CallbackModal onClose={() => setShowCallbackModal(false)} />}
|
{showCallbackModal && <CallbackModal paymentType={paymentType} onClose={() => setShowCallbackModal(false)} />}
|
||||||
</div>
|
</div>
|
||||||
</Providers>
|
</Providers>
|
||||||
</CallbackModalContext.Provider>
|
</CallbackModalContext.Provider>
|
||||||
|
|||||||
@ -28,8 +28,8 @@ export default function PricingPage() {
|
|||||||
/**价格方案 */
|
/**价格方案 */
|
||||||
function HomeModule5() {
|
function HomeModule5() {
|
||||||
const [billingType, setBillingType] = useState<"month" | "year">("month");
|
const [billingType, setBillingType] = useState<"month" | "year">("month");
|
||||||
|
|
||||||
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
||||||
|
const [loadingPlan, setLoadingPlan] = useState<string | null>(null); // 跟踪哪个计划正在加载
|
||||||
|
|
||||||
// 从后端获取订阅计划数据
|
// 从后端获取订阅计划数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -71,6 +71,7 @@ function HomeModule5() {
|
|||||||
}, [plans, billingType]);
|
}, [plans, billingType]);
|
||||||
|
|
||||||
const handleSubscribe = async (planName: string) => {
|
const handleSubscribe = async (planName: string) => {
|
||||||
|
setLoadingPlan(planName); // 设置加载状态
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { createCheckoutSession, redirectToCheckout } = await import(
|
const { createCheckoutSession, redirectToCheckout } = await import(
|
||||||
@ -94,10 +95,14 @@ function HomeModule5() {
|
|||||||
if (!result.successful || !result.data) {
|
if (!result.successful || !result.data) {
|
||||||
throw new Error("create checkout session failed");
|
throw new Error("create checkout session failed");
|
||||||
}
|
}
|
||||||
window.opener?.postMessage({ type: "waiting-payment" }, "*");
|
window.opener?.postMessage({
|
||||||
|
type: "waiting-payment",
|
||||||
|
paymentType: "subscription"
|
||||||
|
}, "*");
|
||||||
// 2. 直接跳转到Stripe托管页面(就这么简单!)
|
// 2. 直接跳转到Stripe托管页面(就这么简单!)
|
||||||
window.location.href = result.data.checkout_url;
|
window.location.href = result.data.checkout_url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setLoadingPlan(null); // 出错时清除加载状态
|
||||||
throw new Error("create checkout session failed, please try again later");
|
throw new Error("create checkout session failed, please try again later");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,13 @@ enum PaymentStatus {
|
|||||||
* 支付回调模态框组件
|
* 支付回调模态框组件
|
||||||
* 处理Stripe Checkout支付完成后的状态展示和用户操作
|
* 处理Stripe Checkout支付完成后的状态展示和用户操作
|
||||||
*/
|
*/
|
||||||
export default function CallbackModal({ onClose }: { onClose: () => void }) {
|
export default function CallbackModal({
|
||||||
|
paymentType = 'subscription',
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
paymentType?: 'subscription' | 'token';
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
|
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
|
||||||
@ -119,6 +125,12 @@ export default function CallbackModal({ onClose }: { onClose: () => void }) {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{paymentInfo?.tokenPurchase && (
|
||||||
|
<>
|
||||||
|
<p>Token Purchase Successful!</p>
|
||||||
|
<p>Your credits will be updated shortly.</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{paymentInfo?.paymentTime && (
|
{paymentInfo?.paymentTime && (
|
||||||
<p>Payment Time: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
|
<p>Payment Time: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
|
||||||
)}
|
)}
|
||||||
@ -154,14 +166,52 @@ export default function CallbackModal({ onClose }: { onClose: () => void }) {
|
|||||||
setPaymentStatus(PaymentStatus.FAILED)
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = JSON.parse(localStorage.getItem('currentUser') || '{}').id
|
const userId = JSON.parse(localStorage.getItem('currentUser') || '{}').id
|
||||||
const res = await getCheckoutSessionStatus(ev.data.sessionId, userId)
|
|
||||||
if (res.successful && res.data) {
|
try {
|
||||||
if(res.data.payment_status === 'success'){
|
if (paymentType === 'subscription') {
|
||||||
setPaymentStatus(PaymentStatus.SUCCESS)
|
// 订阅支付状态查询
|
||||||
}else{
|
const res = await getCheckoutSessionStatus(ev.data.sessionId, userId)
|
||||||
setPaymentStatus(PaymentStatus.FAILED)
|
if (res.successful && res.data) {
|
||||||
|
if (res.data.payment_status === 'success') {
|
||||||
|
setPaymentStatus(PaymentStatus.SUCCESS)
|
||||||
|
setPaymentInfo({
|
||||||
|
orderId: res.data.biz_order_no,
|
||||||
|
sessionId: ev.data.sessionId,
|
||||||
|
paymentTime: res.data.pay_time,
|
||||||
|
subscription: res.data.subscription
|
||||||
|
})
|
||||||
|
} else if (res.data.payment_status === 'fail') {
|
||||||
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
|
}
|
||||||
|
} else if (paymentType === 'token') {
|
||||||
|
// Token购买状态查询
|
||||||
|
const { getTokenPurchaseStatus } = await import('@/lib/stripe')
|
||||||
|
const res = await getTokenPurchaseStatus(ev.data.sessionId)
|
||||||
|
if (res.successful && res.data) {
|
||||||
|
if (res.data.payment_status === 'success') {
|
||||||
|
setPaymentStatus(PaymentStatus.SUCCESS)
|
||||||
|
setPaymentInfo({
|
||||||
|
orderId: ev.data.sessionId,
|
||||||
|
sessionId: ev.data.sessionId,
|
||||||
|
tokenPurchase: { success: true }
|
||||||
|
})
|
||||||
|
// Token购买成功后,延迟刷新页面以更新积分显示
|
||||||
|
setTimeout(() => window.location.reload(), 2000)
|
||||||
|
} else if (res.data.payment_status === 'fail') {
|
||||||
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('支付状态查询失败:', error)
|
||||||
|
setPaymentStatus(PaymentStatus.FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,8 +100,13 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful && response.data?.checkout_url) {
|
if (response.successful && response.data?.checkout_url) {
|
||||||
// 跳转到Stripe支付页面
|
// 通知当前窗口等待支付,标识为Token购买
|
||||||
window.location.href = response.data.checkout_url;
|
window.postMessage({
|
||||||
|
type: "waiting-payment",
|
||||||
|
paymentType: "token"
|
||||||
|
}, "*");
|
||||||
|
// 在新标签页中打开Stripe支付页面
|
||||||
|
window.open(response.data.checkout_url, '_blank');
|
||||||
} else {
|
} else {
|
||||||
console.error("创建Token购买失败:", response.message);
|
console.error("创建Token购买失败:", response.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,12 @@ export interface BuyTokensData {
|
|||||||
|
|
||||||
export type BuyTokensResponse = ApiResponse<BuyTokensData>;
|
export type BuyTokensResponse = ApiResponse<BuyTokensData>;
|
||||||
|
|
||||||
|
export interface TokenPurchaseStatusData {
|
||||||
|
payment_status: "success" | "fail" | "pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TokenPurchaseStatusResponse = ApiResponse<TokenPurchaseStatusData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订阅计划列表
|
* 获取订阅计划列表
|
||||||
* 从后端API获取所有活跃的订阅计划,后端已经过滤了活跃计划
|
* 从后端API获取所有活跃的订阅计划,后端已经过滤了活跃计划
|
||||||
@ -219,6 +225,21 @@ export async function buyTokens(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询Token购买状态
|
||||||
|
*
|
||||||
|
* 用于轮询检查Token购买的支付状态
|
||||||
|
*/
|
||||||
|
export async function getTokenPurchaseStatus(
|
||||||
|
sessionId: string
|
||||||
|
): Promise<TokenPurchaseStatusResponse> {
|
||||||
|
try {
|
||||||
|
return await get<TokenPurchaseStatusResponse>(`/api/payment/token-purchase-status/${sessionId}`);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单的跳转到Customer Portal页面的工具函数
|
* 简单的跳转到Customer Portal页面的工具函数
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user