PAYATHON 2026

MercadoPago's SDK 与 Laravel 5.5 支付偏好错误

支付老李

结论

这两个错误分别由凭证配置和币种设置引起:

  1. Wrong number of parameters:.env 中的 MP_CLIENT_SECRET 被误写成了 MP_CIENT_SECRET,导致 env('MP_CLIENT_SECRET') 返回 null。SDK 因此无法获得完整的 client_id 和 client_secret,也就无法获取访问凭证。
  2. 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、购物车金额和商品信息都应由服务端重新查询和校验,不能信任浏览器提交的金额数据。

备注:内容仅供参考。