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

Next.js集成Easypaisa支付网关返回‘Request Rejected’无法支付

Easypaisa支付网关集成Next.js(App Router)时参数认证失败问题

问题描述

我正在将Easypaisa支付网关集成到Next.js(App Router)应用中。前端支付按钮调用/api/easypaisa/initiate接口生成带哈希值的Easypay URL,但提交请求后并未跳转到Easypay支付页面,而是收到Easypaisa返回的"Request Rejected"页面。服务器无任何日志,说明请求从未返回至我的应用。尝试多种解决方案后仍提示「无效请求」或「Parameter Authentication failed(参数认证失败)」。

已尝试的操作

  • 验证storeId和hashKey为Easypaisa沙箱凭证且正确无误;
  • 确认postBackURL为HTTPS且可公开访问;
  • 尝试使用Easypaisa的staging和production两种URL;
  • 尝试开启和关闭autoRedirect;
  • 检查哈希生成逻辑与官方文档一致。

核心问题

Easypaisa返回"Parameter Authentication failed"的原因是什么?在Next.js中生成支付请求时,哈希值或参数应如何正确构造?

相关代码

EasyPayButton.tsx

"use client";
import { useState } from "react";

export default function EasyPayButton() {
  const [loading, setLoading] = useState(false);

  async function handlePay() {
    setLoading(true);
    try {
      const res = await fetch("/api/easypaisa/initiate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          amount: "100",
          orderRefNum: Date.now().toString(),
        }),
      });

      const data = await res.json();
      if (!res.ok) throw new Error(data.message || "Failed");

      const form = document.createElement("form");
      form.method = "POST";
      form.action = data.easypayURL;
      form.target = "_self";

      for (const key in data.params) {
        const input = document.createElement("input");
        input.type = "hidden";
        input.name = key;
        input.value = data.params[key];
        form.appendChild(input);
      }

      document.body.appendChild(form);
      form.submit();
    } catch (error) {
      console.error("Easypay Error:", error);
      alert("Payment initiation failed");
    } finally {
      setLoading(false);
    }
  }

  return (
    <button onClick={handlePay} disabled={loading}>
      {loading ? "Redirecting..." : "Pay with Easypaisa"}
    </button>
  );
}   

/api/easypaisa/initiate.ts

import type { NextApiRequest, NextApiResponse } from "next";
import crypto from "crypto";


const storeId = process.env.NEXT_PUBLIC_EASYPAY_STORE_ID!;
const hashKey = process.env.NEXT_PUBLIC_EASYPAY_HASH_KEY!;
const postBackURL = "https://1shot.pk/api/easypaisa/response";
const autoRedirect = "1";

function formatExpiryDate(): string {
  const now = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes ahead
  const yyyyMMdd = now.toISOString().slice(0, 10).replace(/-/g, ''); // "20251028"
  const HHmmss = now.toTimeString().slice(0, 8).replace(/:/g, '');   // "072400"
  return `${yyyyMMdd} ${HHmmss}`; // "20251028 072400"
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    if (req.method !== "POST") {
        return res.status(405).json({ message: "Method not allowed" });
    }

    try {
        const { amount, orderRefNum } = req.body as {
            amount: string;
            orderRefNum: string;
        };

        if (!storeId || !hashKey) {
            return res.status(500).json({ message: "Easypay store ID or hash key not configured" });
        }

        const expiryDate = formatExpiryDate();
        const payload: Record<string, string> = {
            amount: Number(amount).toFixed(2),
            storeId,
            postBackURL,
            orderRefNum,
            expiryDate,
            autoRedirect,
            emailAddr: "test@example.com",
            mobileNum: "03001234567",
        };
        const sortedKeys = Object.keys(payload).sort();
        const rawString = sortedKeys.map(key => `${key}=${payload[key]}`).join("&") + `&${hashKey}`;
        const merchantHashedReq = crypto.createHash("sha256").update(rawString).digest("hex");
        const paramsObj = {
            ...payload,
            merchantHashedReq,
        };
        return res.status(200).json({ params: paramsObj });
    } catch (error) {
        console.error("Easypay Initiate Error:", error);
        return res.status(500).json({ message: "Internal Server Error" });
    }
}

解决方案

参数认证失败的核心原因及修复步骤

1. 哈希生成逻辑的关键错误

你的代码中哈希拼接存在问题:Easypaisa要求将hashKey直接追加在拼接后的参数字符串末尾,不需要加&。当前代码里的rawString多了一个&符号,这会导致哈希值完全错误,直接触发参数认证失败。

修复代码:
将initiate.ts中的这一行:

const rawString = sortedKeys.map(key => `${key}=${payload[key]}`).join("&") + `&${hashKey}`;

改为:

const rawString = sortedKeys.map(key => `${key}=${payload[key]}`).join("&") + hashKey;

2. 环境变量的安全性与正确性

你把storeId和hashKey放在了NEXT_PUBLIC_前缀的环境变量中,这会导致这些敏感信息暴露在前端代码里,不仅有安全风险,还可能因为前端环境变量的处理方式导致值被意外篡改。

修复方式:

  • 去掉环境变量的NEXT_PUBLIC_前缀,改为EASYPAY_STORE_ID和EASYPAY_HASH_KEY;
  • 在initiate.ts中直接读取这些非公开环境变量:
    const storeId = process.env.EASYPAY_STORE_ID!;
    const hashKey = process.env.EASYPAY_HASH_KEY!;
    

3. 表单提交的目标URL缺失

前端代码中form.action = data.easypayURL,但你的后端接口并未返回easypayURL字段,这会导致表单提交到空地址,实际并未发送到Easypaisa的网关。

修复方式:
在initiate.ts的返回结果中添加Easypaisa沙箱或生产环境的URL:

  • 沙箱环境:https://easypaystg.easypaisa.com.pk/easypay/Index.js
  • 生产环境:https://easypay.easypaisa.com.pk/easypay/Index.js

修改后端返回代码:

return res.status(200).json({ 
  easypayURL: "https://easypaystg.easypaisa.com.pk/easypay/Index.js",
  params: paramsObj 
});

4. 日期格式的潜在问题

formatExpiryDate函数中使用toTimeString()获取时间,这可能会受到服务器时区的影响。Easypaisa要求使用**UTC+5时区(巴基斯坦标准时间)**的时间戳,否则会导致过期时间验证失败。

修复代码:

function formatExpiryDate(): string {
  const now = new Date();
  now.setMinutes(now.getMinutes() + 5); // 5分钟后过期
  // 转换为巴基斯坦标准时间(UTC+5)
  const pstOffset = 5 * 60 * 60 * 1000;
  const pstTime = new Date(now.getTime() + pstOffset);
  
  const yyyyMMdd = pstTime.toISOString().slice(0, 10).replace(/-/g, '');
  const HHmmss = pstTime.toTimeString().slice(0, 8).replace(/:/g, '');
  return `${yyyyMMdd} ${HHmmss}`;
}

5. 参数大小写与必填项验证

确认所有参数的大小写与Easypaisa官方文档完全一致,比如emailAddr、mobileNum等字段的拼写不能出错;同时确保orderRefNum是唯一值,不能重复提交相同的订单号。

验证步骤

  1. 修复上述问题后,在后端打印rawString和merchantHashedReq,对比官方文档的哈希生成示例,确认哈希值正确;
  2. 使用Postman直接调用/api/easypaisa/initiate接口,获取参数后手动构造表单提交到Easypaisa沙箱地址,测试是否能跳转;
  3. 检查Easypaisa沙箱后台的日志,查看具体的错误详情(如果有)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 03:09:53