MercadoPago's SDK 与 Laravel 5.5 支付偏好错误
结论
这两个错误分别由凭证配置和币种设置引起:
Wrong number of parameters:.env中的MP_CLIENT_SECRET被误写成了MP_CIENT_SECRET,导致env('MP_CLIENT_SECRET')返回null。SDK 因此无法获得完整的client_id和client_secret,也就无法获取访问凭证。currency_id invalid:当前 Mercado Pago 账户所属站点或使用的支付接口不支持VEF。currency_id不能随意填写,必须使用账户所在 Mercado Pago 市场支持的币种,如ARS、BRL、MXN、COP、CLP、PEN或UYU。具体值取决于账户所属国家或站点。
如果业务必须使用委内瑞拉币种,需要先确认 Mercado Pago 是否支持该市场。不能简单地把 VEF 换成现在的委内瑞拉货币代码 VES。即使 ISO 代码有效,只要当前 Mercado Pago 站点不支持,请求仍会被拒绝。
修正 Mercado Pago 凭证
先确保 .env 中的变量名正确:
MP_CLIENT_ID=your_client_id
MP_CLIENT_SECRET=your_client_secret
MP_NOTIFICATION_URL=https://example.com/mercadopago/notifications
APP_URL=https://example.com
Laravel 可能仍在使用之前缓存的配置。修改 .env 后,清理配置缓存:
php artisan config:clear
生产环境通常可以再重新生成配置缓存:
php artisan config:cache
建议把环境变量统一映射到配置文件,不要在控制器中反复调用 env()。可以在 config/services.php 中加入:
'mercadopago' => [
'client_id' => env('MP_CLIENT_ID'),
'client_secret' => env('MP_CLIENT_SECRET'),
'notification_url' => env('MP_NOTIFICATION_URL'),
],
然后在控制器中读取配置:
$mp = new MP(
config('services.mercadopago.client_id'),
config('services.mercadopago.client_secret')
);
创建 SDK 实例前还可以主动检查配置。这样遇到空值时,应用会直接给出明确的错误,而不是等 API 返回难以判断原因的提示:
$clientId = config('services.mercadopago.client_id');
$clientSecret = config('services.mercadopago.client_secret');
if (empty($clientId) || empty($clientSecret)) {
throw new \RuntimeException('Mercado Pago credentials are not configured.');
}
$mp = new MP($clientId, $clientSecret);
正确设置 currency_id
currency_id 必须与 Mercado Pago 账户所属站点一致。常见的对应关系如下:
| 账户市场 | 常用 currency_id |
|---|---|
| 阿根廷 | ARS |
| 巴西 | BRL |
| 墨西哥 | MXN |
| 哥伦比亚 | COP |
| 智利 | CLP |
| 秘鲁 | PEN |
| 乌拉圭 | UYU |
可用币种还会受到账户国家、API 产品以及 Mercado Pago 当时支持范围的影响,请以账户后台和对应站点的 API 文档为准。
VEF 是委内瑞拉玻利瓦尔的旧 ISO 代码。Mercado Pago 也不允许账户通过 currency_id 任意选择法定货币。如果账户属于其他国家,即使填写了有效的 ISO 货币代码,也可能收到 currency_id invalid。
例如,使用哥伦比亚 Mercado Pago 账户时,应设置为:
'currency_id' => 'COP',
不要根据商品页面上的展示币种猜测这个字段,也不要为了消除报错而随意改成 USD。支付偏好、账户站点和商品金额所用的币种必须彼此兼容。
创建 payment preference 的示例
以下示例使用哥伦比亚账户和 COP:
public function process(Request $request)
{
$request->validate([
'ctoken' => 'required|string',
]);
$clientId = config('services.mercadopago.client_id');
$clientSecret = config('services.mercadopago.client_secret');
if (empty($clientId) || empty($clientSecret)) {
throw new \RuntimeException('Mercado Pago credentials are not configured.');
}
$mp = new MP($clientId, $clientSecret);
$user = auth()->user();
$token = $request->input('ctoken');
$entries = Cart::where('session_id', $token)->get();
if ($entries->isEmpty()) {
return back()->withErrors([
'payment' => 'The cart is empty.',
]);
}
$items = [];
foreach ($entries as $entry) {
$items[] = [
'title' => (string) $entry->product_name,
'category_id' => 'zapato',
'quantity' => (int) $entry->qty,
'currency_id' => 'COP',
'unit_price' => (float) $entry->price,
];
}
$preferenceData = [
'external_reference' => 'VSHOPREF-' . $token,
'payer' => [
'name' => $user->name,
'email' => $user->email,
],
'items' => $items,
'back_urls' => [
'success' => url('/gracias'),
'pending' => url('/gracias'),
'failure' => url('/error'),
],
'notification_url' => config(
'services.mercadopago.notification_url'
),
'auto_return' => 'all',
];
$preference = $mp->create_preference($preferenceData);
if (
empty($preference['response']) ||
empty($preference['response']['init_point'])
) {
throw new \RuntimeException(
'Mercado Pago did not return a payment URL.'
);
}
return redirect()->away(
$preference['response']['init_point']
);
}
还需要检查的事项
quantity应传入整数,unit_price应传入数值,不要直接提交数据库中的原始字符串。- 同一个 preference 中的商品应使用相同且受账户支持的币种。
notification_url必须是 Mercado Pago 能从公网访问的地址,不能使用localhost。- 不要混用测试凭证和生产凭证。测试付款通常还需要使用与凭证匹配的测试账户。
- 旧版 PHP SDK 通常使用
client_id和client_secret初始化,新版 SDK 可能已经改用 access token。升级 SDK 后,应按已安装版本的接口调整代码,不能混用不同版本的示例。 ctoken、购物车金额和商品信息都应由服务端重新查询和校验,不能信任浏览器提交的金额数据。
备注:内容仅供参考。