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

Next.js中res.redirect is not a function错误及支付重定向问题

问题分析与解决方案

错误根源

  1. API路由类型不匹配:你在Next.js App Router的API路由(app/api/.../route.ts)中,错误使用了Pages Router的NextApiRequest/NextApiResponse类型。App Router的API响应对象没有redirect方法,也不支持writeHead这类旧写法,导致TypeError。
  2. 前端跳转逻辑问题:前端用fetch调用API时,后端返回的重定向只会作用于fetch请求本身,不会触发浏览器页面跳转,所以即使支付发起成功,用户也看不到支付网关页面。

解决方案(推荐方案:后端返回支付URL,前端跳转)

这种方式更灵活,便于处理错误和用户提示。

1. 修正后端route.ts(适配App Router)

import { NextRequest, NextResponse } from 'next/server';
import * as crypto from 'crypto';
import axios from 'axios';

export async function POST(req: NextRequest) {
  try {
    console.log('支付初始化');
    // 解析请求体(App Router需手动调用json())
    const body = await req.json();
    const merchantTransactionId = body.transactionId || `TXN-${Date.now()}`;

    const data = {
      merchantId: process.env.PHONEPE_MERCHANT_ID,
      merchantTransactionId,
      merchantUserId: body.MUID || 'default-user',
      name: body.name || '测试用户',
      amount: 23 * 100, // 单位为分
      redirectUrl: `http://localhost:3000/api/status/${merchantTransactionId}`, // 注意端口与你的Next.js服务一致
      redirectMode: 'POST',
      mobileNumber: body.mobileNumber || '9000000000',
      paymentInstrument: {
        type: 'PAY_PAGE',
      },
    };

    // 生成PhonePe要求的校验参数
    const payload = JSON.stringify(data);
    const payloadMain = Buffer.from(payload).toString('base64');
    const keyIndex = process.env.PHONEPE_KEY_INDEX;
    const string = payloadMain + '/pg/v1/pay' + process.env.PHONEPE_SALT_KEY;
    const sha256 = crypto.createHash('sha256').update(string).digest('hex');
    const checksum = sha256 + '###' + keyIndex;

    const prod_URL = 'https://api-preprod.phonepe.com/apis/hermes/pg/v1/pay';

    const options = {
      method: 'POST',
      url: prod_URL,
      headers: {
        accept: 'application/json',
        'Content-Type': 'application/json',
        'X-VERIFY': checksum,
      },
      data: {
        request: payloadMain,
      },
    };

    // 异步调用PhonePe接口(必须await,避免请求未完成就返回)
    const response = await axios.request(options);
    const paymentUrl = response.data.data.instrumentResponse.redirectInfo.url;

    // 返回支付URL给前端
    return NextResponse.json({ paymentUrl });
  } catch (error) {
    console.error('支付初始化失败:', error);
    return NextResponse.json(
      { message: error instanceof Error ? error.message : '服务器内部错误' },
      { status: 500 }
    );
  }
}

2. 修正前端页面(拿到URL后跳转)

'use client';
import { Button } from '@/components/ui/button';
import React from 'react';

function Page() {
  async function createPayment() {
    try {
      const response = await fetch(`/api/payments/phonepe`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        // 传递支付所需参数(根据业务需求调整)
        body: JSON.stringify({
          transactionId: `TXN-${Date.now()}`,
          MUID: 'user-123',
          name: '张三',
          mobileNumber: '9000000000',
        }),
      });

      if (!response.ok) {
        throw new Error('支付初始化失败');
      }

      const data = await response.json();
      // 跳转到PhonePe支付页面
      window.location.href = data.paymentUrl;
    } catch (error) {
      console.error('错误:', error);
      alert('支付启动失败,请稍后重试');
    }
  }

  return (
    <div>
      <Button onClick={createPayment}>结算付款</Button>
    </div>
  );
}

export default Page;

备选方案:表单提交+后端直接重定向

如果不需要前端额外处理逻辑,可改用表单提交,让浏览器直接处理后端重定向:

1. 前端页面修改为表单提交

'use client';
import { Button } from '@/components/ui/button';
import React from 'react';

function Page() {
  return (
    <div>
      <form action="/api/payments/phonepe" method="POST">
        {/* 隐藏字段传递参数 */}
        <input type="hidden" name="transactionId" value={`TXN-${Date.now()}`} />
        <input type="hidden" name="MUID" value="user-123" />
        <Button type="submit">结算付款</Button>
      </form>
    </div>
  );
}

export default Page;

2. 后端route.ts修改为直接重定向

import { NextRequest, NextResponse } from 'next/server';
import * as crypto from 'crypto';
import axios from 'axios';

export async function POST(req: NextRequest) {
  try {
    console.log('支付初始化');
    // 解析表单数据
    const formData = await req.formData();
    const merchantTransactionId = formData.get('transactionId') as string || `TXN-${Date.now()}`;

    const data = {
      merchantId: process.env.PHONEPE_MERCHANT_ID,
      merchantTransactionId,
      merchantUserId: formData.get('MUID') as string || 'default-user',
      name: '测试用户',
      amount: 23 * 100,
      redirectUrl: `http://localhost:3000/api/status/${merchantTransactionId}`,
      redirectMode: 'POST',
      mobileNumber: '9000000000',
      paymentInstrument: {
        type: 'PAY_PAGE',
      },
    };

    // 生成校验参数(同推荐方案)
    const payload = JSON.stringify(data);
    const payloadMain = Buffer.from(payload).toString('base64');
    const keyIndex = process.env.PHONEPE_KEY_INDEX;
    const string = payloadMain + '/pg/v1/pay' + process.env.PHONEPE_SALT_KEY;
    const sha256 = crypto.createHash('sha256').update(string).digest('hex');
    const checksum = sha256 + '###' + keyIndex;

    const prod_URL = 'https://api-preprod.phonepe.com/apis/hermes/pg/v1/pay';

    const options = {
      method: 'POST',
      url: prod_URL,
      headers: {
        accept: 'application/json',
        'Content-Type': 'application/json',
        'X-VERIFY': checksum,
      },
      data: {
        request: payloadMain,
      },
    };

    const response = await axios.request(options);
    const paymentUrl = response.data.data.instrumentResponse.redirectInfo.url;

    // 直接重定向到支付页面
    return NextResponse.redirect(paymentUrl);
  } catch (error) {
    console.error('支付初始化失败:', error);
    return NextResponse.json(
      { message: error instanceof Error ? error.message : '服务器内部错误' },
      { status: 500 }
    );
  }
}

注意事项

  • 确保.env.local中正确配置PHONEPE_MERCHANT_ID、PHONEPE_KEY_INDEX、PHONEPE_SALT_KEY三个环境变量
  • 交易ID需保证全局唯一,建议用时间戳+用户ID生成,不要写死固定值
  • 回调地址redirectUrl需与你的服务域名/端口一致,且要能处理PhonePe的回调请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 13:39:53