PAYATHON 2026

Laravel 表单无法从数组获取 m_shop 变量

支付老李

结论

问题并不是 $arHash 无法读取 $m_shop。这个变量已经正确放入 PHP 数组,签名计算也不存在明显的数组取值错误。

报错的原因是,代码虽然使用 $m_shop 等变量计算了 $sign,却没有把这些参数放进表单并提交给支付网关。当前的 $data['send_pay_request'] 基本是空字符串,所以 Payeer 收到的请求中没有 m_shop,随后返回:

m_shop is invalid or empty

删除 m_shop 不能解决问题,因为它是必填参数。更换 Shop ID 也没有作用,因为代码根本没有提交这个字段。

正确的处理方式

需要处理两件事:

  1. 按照 Payeer 规定的参数顺序计算签名。
  2. 使用 POST 表单提交 m_shop、m_orderid、m_amount、m_curr、m_desc 和 m_sign。

控制器不必拼接整段 HTML。可以把支付参数传给 Blade 模板,再由模板生成并自动提交表单。

控制器示例

public function gatewayRedirect(Request $request)
{
    $id = session('payment_log_id');

    $trans = PaymentLog::findOrFail($id);
    $gateway = PaymentMethod::findOrFail($trans->payment_type);

    if ($gateway->id != 1) {
        abort(400, 'Unsupported payment gateway.');
    }

    $m_shop = '547754002';
    $m_orderid = (string) $trans->id;
    $m_amount = number_format(100, 2, '.', '');
    $m_curr = 'USD';
    $m_desc = base64_encode('Test');
    $m_key = '123';

    $arHash = [
        $m_shop,
        $m_orderid,
        $m_amount,
        $m_curr,
        $m_desc,
        $m_key,
    ];

    $m_sign = strtoupper(
        hash('sha256', implode(':', $arHash))
    );

    $paymentData = [
        'm_shop' => $m_shop,
        'm_orderid' => $m_orderid,
        'm_amount' => $m_amount,
        'm_curr' => $m_curr,
        'm_desc' => $m_desc,
        'm_sign' => $m_sign,
    ];

    return view('user.autoredirectgateway', [
        'page_title' => 'Deposit Processing',
        'paymentData' => $paymentData,
        'paymentUrl' => 'https://payeer.com/merchant/',
    ]);
}

示例使用 $trans->id 作为订单号。实际项目应改用唯一且不会重复的支付订单号。

支付地址应以当前 Payeer 商户接口文档和账户配置为准。如果使用的接口提供了其他正式地址,需要相应替换 paymentUrl。

Blade 自动提交表单

在 resources/views/user/autoredirectgateway.blade.php 中输出表单:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{{ $page_title }}</title>
</head>
<body>
    <p>正在跳转到支付页面,请稍候……</p>

    <form
        id="payeer-payment-form"
        method="POST"
        action="{{ $paymentUrl }}"
    >
        <input type="hidden" name="m_shop" value="{{ $paymentData['m_shop'] }}">
        <input type="hidden" name="m_orderid" value="{{ $paymentData['m_orderid'] }}">
        <input type="hidden" name="m_amount" value="{{ $paymentData['m_amount'] }}">
        <input type="hidden" name="m_curr" value="{{ $paymentData['m_curr'] }}">
        <input type="hidden" name="m_desc" value="{{ $paymentData['m_desc'] }}">
        <input type="hidden" name="m_sign" value="{{ $paymentData['m_sign'] }}">

        <noscript>
            <button type="submit">继续支付</button>
        </noscript>
    </form>

    <script>
        document.getElementById('payeer-payment-form').submit();
    </script>
</body>
</html>

不要在发送给 Payeer 的表单中加入 Laravel 的 @csrf,除非接收方明确要求该字段。@csrf 用来保护提交到 Laravel 应用自身的请求,不属于 Payeer 的支付参数。

如果必须继续拼接 HTML

如果现有项目依赖 {!! $send_pay_request !!} 输出,也可以直接构造表单。不过,这种方式的可维护性和安全性不如在 Blade 中输出字段:

$data['send_pay_request'] = sprintf(
    '<form id="payeer-form" method="POST" action="https://payeer.com/merchant/">
        <input type="hidden" name="m_shop" value="%s">
        <input type="hidden" name="m_orderid" value="%s">
        <input type="hidden" name="m_amount" value="%s">
        <input type="hidden" name="m_curr" value="%s">
        <input type="hidden" name="m_desc" value="%s">
        <input type="hidden" name="m_sign" value="%s">
    </form>
    <script>document.getElementById("payeer-form").submit();</script>',
    e($m_shop),
    e($m_orderid),
    e($m_amount),
    e($m_curr),
    e($m_desc),
    e($m_sign)
);

模板必须使用未转义输出,否则表单会显示成普通文本:

{!! $send_pay_request !!}

只有当这段 HTML 完全由服务端可信数据生成时,才应使用这种写法。所有来自用户的内容都必须先转义。

需要重点检查的地方

1. 表单字段名必须完全一致

支付网关读取的是表单字段名称,而不是控制器中的 PHP 变量名。至少要按照接口要求提交以下字段:

m_shop
m_orderid
m_amount
m_curr
m_desc
m_sign

例如,下面的代码只声明了变量,并不会自动发送它:

$m_shop = '547754002';

必须把变量放入表单字段或请求体,才算真正提交:

<input type="hidden" name="m_shop" value="547754002">

2. 签名参数的顺序不能改变

计算签名时,要按照以下顺序用冒号连接参数,再计算 SHA-256:

m_shop:m_orderid:m_amount:m_curr:m_desc:m_key

对应代码如下:

$arHash = [
    $m_shop,
    $m_orderid,
    $m_amount,
    $m_curr,
    $m_desc,
    $m_key,
];

$m_sign = strtoupper(hash('sha256', implode(':', $arHash)));

$m_key 是商户密钥,只参与签名计算,不能作为表单字段发送到浏览器或 Payeer。

如果还使用了 m_params 等可选字段,需要根据当前接口文档确定它是否参与签名,以及应该放在什么位置。

3. 金额格式必须保持一致

签名时使用的金额必须与表单提交的金额完全相同:

$m_amount = number_format(100, 2, '.', '');

结果是:

100.00

如果签名使用 100.00,表单却提交 100,通常会导致签名校验失败。

4. Shop ID 和密钥不要混用

m_shop 应填写商户标识,m_key 应填写对应商户的密钥,而且两者必须属于同一套商户配置。测试环境与正式环境使用不同凭据时,也不能交叉使用。

实际项目不应把密钥直接写进控制器,可以将其放入 .env:

PAYEER_SHOP_ID=547754002
PAYEER_SECRET_KEY=your-secret-key
PAYEER_PAYMENT_URL=https://payeer.com/merchant/

再从配置文件中读取环境变量:

// config/services.php

'payeer' => [
    'shop_id' => env('PAYEER_SHOP_ID'),
    'secret_key' => env('PAYEER_SECRET_KEY'),
    'payment_url' => env(
        'PAYEER_PAYMENT_URL',
        'https://payeer.com/merchant/'
    ),
],

控制器中这样使用:

$m_shop = config('services.payeer.shop_id');
$m_key = config('services.payeer.secret_key');
$paymentUrl = config('services.payeer.payment_url');

修改 .env 后,如果项目启用了配置缓存,还需要重新生成配置缓存,否则 Laravel 可能继续读取旧值。

排查请求是否真的带上了参数

在浏览器开发者工具的 Network 面板中找到提交给 Payeer 的请求,然后检查 Request Payload 或 Form Data。正常请求应包含:

m_shop: 547754002
m_orderid: ...
m_amount: 100.00
m_curr: USD
m_desc: VGVzdA==
m_sign: ...

如果其中没有 m_shop,说明问题仍然出在表单生成或自动提交逻辑。如果请求已经包含 m_shop,但网关仍提示该字段无效,就需要继续核对 Shop ID、请求地址、账户状态,以及测试环境和正式环境是否匹配。

备注:内容仅供参考。