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

如何在Next.js网站中获取任意UPI应用的支付状态(成功/失败)

解决Next.js中UPI支付状态回调的实现方案

核心逻辑说明

UPI支付的状态校验必须依赖商户后台的Webhook回调,前端深度链接仅用于同步页面状态,不能作为最终支付结果的判断依据(前端状态可被篡改)。以下是具体实现步骤:


1. 配置UPI商户Webhook回调接口

在你的Next.js项目中创建API路由(替代PHP后端),用于接收UPI网关发送的支付状态通知:

  • 路径:pages/api/upi-webhook.js(App Router则用app/api/upi-webhook/route.js)
  • 核心功能:
    • 验证UPI网关传递的签名(防止伪造请求,具体签名规则参考你的商户服务商文档)
    • 根据回调中的订单ID(tn参数)更新数据库内的订单状态(成功/失败/超时)
    • 返回UPI网关要求的响应格式(一般是{"status":"SUCCESS"})

示例代码(App Router):

export async function POST(request) {
  const body = await request.json();
  // 1. 验证签名(示例逻辑,需替换为服务商实际规则)
  const isValid = verifySignature(body, process.env.UPI_MERCHANT_SECRET);
  if (!isValid) return new Response(JSON.stringify({status: "FAILED"}), {status: 403});

  // 2. 更新数据库订单状态
  const orderId = body.tn; // 对应支付时传递的tn参数
  await updateOrderStatus(orderId, body.status);

  // 3. 返回成功响应
  return new Response(JSON.stringify({status: "SUCCESS"}), {status: 200});
}

2. 发起支付时关联唯一订单ID

调用upi://pay链接时,必须通过tn参数传递你的业务订单ID,确保UPI网关回调时能关联到具体订单:

const handleInitiatePayment = (orderId) => {
  const upiUrl = `upi://pay?pa=你的商户UPI ID&pn=商户名称&am=100.00&tn=${orderId}&cu=INR`;
  window.location.href = upiUrl;
  // 启动轮询兜底逻辑(见步骤4)
  startPaymentPolling(orderId);
};

3. 深度链接实现前端状态同步

配置UPI商户后台的支付完成跳转地址,让用户支付后回到你的网站并触发状态更新:

  • 跳转地址可以是HTTPS链接(兼容性更好):https://你的域名.com/payment-callback?orderId=ORDER_12345
  • 在Next.js中创建/payment-callback页面,逻辑如下:
    'use client';
    import { useEffect, useState } from 'react';
    import { useSearchParams, useRouter } from 'next/navigation';
    
    export default function PaymentCallback() {
      const searchParams = useSearchParams();
      const router = useRouter();
      const [status, setStatus] = useState('LOADING');
    
      useEffect(() => {
        const orderId = searchParams.get('orderId');
        if (!orderId) return;
        
        // 调用后端接口查询最终支付状态
        fetch(`/api/check-payment?orderId=${orderId}`)
          .then(res => res.json())
          .then(data => {
            if (data.status === 'SUCCESS') {
              router.push('/payment-success');
            } else {
              router.push('/payment-failed');
            }
          });
      }, [searchParams, router]);
    
      return <div>正在查询支付状态...</div>;
    }
    

4. 轮询兜底(必加)

部分UPI APP可能无法触发跳转,或用户手动返回网站,此时需要前端轮询后端接口获取状态:

const startPaymentPolling = (orderId) => {
  let attempt = 0;
  const maxAttempts = 10; // 最多轮询10次(约30秒)
  
  const poll = async () => {
    attempt++;
    const res = await fetch(`/api/check-payment?orderId=${orderId}`);
    const data = await res.json();
    
    if (data.status === 'SUCCESS' || data.status === 'FAILED' || attempt >= maxAttempts) {
      // 停止轮询,跳转对应页面
      router.push(data.status === 'SUCCESS' ? '/payment-success' : '/payment-failed');
      return;
    }
    
    // 3秒后继续轮询
    setTimeout(poll, 3000);
  };
  
  poll();
};

关键注意事项

  • 签名验证不可省略:必须校验UPI网关回调的签名,防止恶意篡改订单状态
  • 最终状态以Webhook为准:前端轮询和深度链接仅用于用户体验优化,订单状态的最终判断必须基于后端数据库
  • 超时处理:后端需设置订单超时时间(如15分钟),超时后自动标记为无效订单

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:12:41