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 也没有作用,因为代码根本没有提交这个字段。
正确的处理方式
需要处理两件事:
- 按照 Payeer 规定的参数顺序计算签名。
- 使用
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、请求地址、账户状态,以及测试环境和正式环境是否匹配。
备注:内容仅供参考。
