PAYATHON 2026

Xsolla Payment CURLOPT_POSTFIELDS 返回 422 错误

支付小周

结论

这里有两个问题需要修正:

  1. CURLOPT_POSTFIELDS 不能在声明 Content-Type: application/json 的同时直接接收 PHP 数组。需要先用 json_encode() 将数组编码为 JSON。
  2. purchase.virtual_items.items 必须是 JSON 数组,不能是单个 JSON 对象。因此,PHP 中需要在数组内放置商品对象:
'items' => array(
    array(
        'sku' => '1'
    )
)

正确的数据结构

$data = array(
    'user' => array(
        'id' => array(
            'value' => '1234567'
        )
    ),
    'settings' => array(
        'project_id' => 29039
    ),
    'purchase' => array(
        'virtual_items' => array(
            'items' => array(
                array(
                    'sku' => '1'
                )
            )
        )
    )
);

编码后,items 在 JSON 中会表示为数组:

{
  "purchase": {
    "virtual_items": {
      "items": [
        {
          "sku": "1"
        }
      ]
    }
  }
}

下面这种写法表示对象:

"items": { "sku": "1" }

API 要求的是数组,因此应写成:

"items": [
  { "sku": "1" }
]

完整请求示例

<?php

$merchantId = 'YOUR_MERCHANT_ID';
$apiKey = 'YOUR_API_KEY';

$data = array(
    'user' => array(
        'id' => array(
            'value' => '1234567'
        )
    ),
    'settings' => array(
        'project_id' => 29039
    ),
    'purchase' => array(
        'virtual_items' => array(
            'items' => array(
                array(
                    'sku' => '1'
                )
            )
        )
    )
);

$dataJson = json_encode($data);

if ($dataJson === false) {
    throw new RuntimeException(
        'JSON encoding failed: ' . json_last_error_msg()
    );
}

$ch = curl_init();

curl_setopt_array($ch, array(
    CURLOPT_URL => sprintf(
        'https://api.xsolla.com/merchant/merchants/%s/token',
        rawurlencode($merchantId)
    ),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $dataJson,
    CURLOPT_USERPWD => $merchantId . ':' . $apiKey,
    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
    CURLOPT_HTTPHEADER => array(
        'Content-Type: application/json',
        'Accept: application/json',
        'Content-Length: ' . strlen($dataJson)
    )
));

$result = curl_exec($ch);

if ($result === false) {
    throw new RuntimeException('cURL error: ' . curl_error($ch));
}

$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpStatus < 200 || $httpStatus >= 300) {
    throw new RuntimeException(
        'Xsolla API returned HTTP ' . $httpStatus . ': ' . $result
    );
}

echo $result;

错误原因

第一次请求把 PHP 数组直接传给了 CURLOPT_POSTFIELDS。这种情况下,cURL 通常会按表单数据处理数组,而不会自动生成 JSON。但请求头声明的是:

Content-Type: application/json

请求体与声明的内容类型不一致,服务端无法将其解析为 JSON,也就读不到 settings.project_id,所以返回“参数缺失”。

改用 json_encode() 后,请求体已经是有效的 JSON。不过,原有结构会生成:

"items": {
  "sku": "1"
}

Xsolla 的 JSON Schema 要求 items 是数组,因此会返回:

Object value found, but an array is required

在商品对象外再包一层数字索引数组,就能生成接口要求的 [...]。

注意事项

  • 声明 Content-Type: application/json 后,不要将 PHP 数组直接传给 CURLOPT_POSTFIELDS。
  • 即使只有一个商品,items 也必须是数组。
  • 添加多个商品时,可以继续向数组中追加:
'items' => array(
    array('sku' => '1'),
    array('sku' => '2')
)
  • project_id 应按照接口要求保留为整数,不要无故改成字符串。
  • 示例中的商户 ID、API Key、项目 ID 和 SKU 都需要替换为真实值。不要把 API Key 输出到日志或错误页面。
  • Content-Length 通常由 cURL 自动计算,不是必需项,保留或删除都可以。

备注:内容仅供参考。