PAYATHON 2026

Pesapal 报错 oauth_parameters_rejected 如何解决?

支付阿杰

结论

报错的直接原因是 pesapal_request_data 中没有有效的订单 XML:

$post_xml = "  ";

Pesapal 收到的只是空白字符串,无法解析 request_xml_data,因此返回:

parameter_rejected
oauth_parameters_rejected
request_xml_data

代码中还有几处需要排查:Consumer Key 和 Consumer Secret 可能填反,reference 被写死,金额格式、回调地址、必填联系方式和 iframe 输出也可能有问题。

需要重点检查的参数

1. Consumer Key 和 Consumer Secret

下面的赋值很可能有误:

$pesapal_consumer_key = $listingpro_options['pesapal_secrit_key'];
$pesapal_consumer_secret = $listingpro_options['pesapal_pubishable_key'];

Consumer Key 必须填写 Pesapal 提供的 Consumer Key,Consumer Secret 也必须对应其 Consumer Secret。不要根据配置项中 “secret key” 和 “publishable key” 的名称交叉填写。

还要注意:

  • 测试凭据只能用于测试环境。
  • 正式凭据只能用于正式环境。
  • Key 前后不能有空格或换行。
  • 如果问题中的代码与实际代码完全一致,那么变量名前还缺少 $。

配置项最好使用明确的名称:

$consumer_key    = trim($listingpro_options['pesapal_consumer_key']);
$consumer_secret = trim($listingpro_options['pesapal_consumer_secret']);

Key 或 Secret 不正确时,OAuth 签名也会被拒绝。

2. pesapal_request_data 不能是空字符串

当前代码并没有生成订单 XML:

$post_xml = "  ";

旧版 PostPesapalDirectOrderV4 接口要求传入完整的 PesapalDirectOrderInfo XML,其中至少要有金额、说明、交易类型、唯一订单号和联系方式。

3. 金额不能包含千位分隔符

number_format() 默认会添加千位分隔符。例如,1200 可能被格式化为:

1,200.00

这可能不符合接口要求。应明确指定小数点,并将千位分隔符设为空字符串:

$amount = number_format((float) $_SESSION['price'], 2, '.', '');

金额还必须大于零。

4. Reference 必须唯一

下面这种固定值不适合真实交易:

$reference = '1053';

反复提交相同的 Reference 可能造成订单冲突。可以使用 WordPress 订单 ID、数据库主键或其他可追踪的唯一编号:

$reference = 'ORDER-' . (int) $_SESSION['post_id'] . '-' . time();

在生产系统中,更稳妥的做法是先创建本地订单,再直接使用该订单的唯一编号,而不是依赖 time()。

5. Email 或 PhoneNumber 至少提供一个

代码同时传入了邮箱和手机号,但仍需确认这些值真实有效:

$email       = $current_user->user_email;
$phonenumber = '1122336655';

不要长期使用占位手机号。如果没有手机号,就要保证 $email 非空;如果用户可能没有邮箱,则需要提供有效手机号。

6. Callback URL 必须有效

$callback_url = 'http://test.com/serviceexpert/redirect.php';

回调地址必须是当前网站上真实存在,并且 Pesapal 能够访问的 URL。生产环境建议使用 HTTPS:

$callback_url = home_url('/pesapal/callback/');

签名完成后不要修改回调地址。Pesapal 请求中的 oauth_callback 必须与参与签名的值完全一致。

正确构造 request_xml_data

下面的示例适用于代码中使用的旧版 PostPesapalDirectOrderV4 OAuth 1.0 接口。如果 Pesapal 商户后台要求使用另一代 API,应以对应版本的接口文档为准,不要混用两套请求格式。

<?php

$consumer_key    = trim($listingpro_options['pesapal_consumer_key']);
$consumer_secret = trim($listingpro_options['pesapal_consumer_secret']);

$token  = null;
$params = null;

$signature_method = new OAuthSignatureMethod_HMAC_SHA1();
$iframelink = 'https://demo.pesapal.com/api/PostPesapalDirectOrderV4';

$current_user = wp_get_current_user();

$amount = number_format(
    (float) ($_SESSION['price'] ?? 0),
    2,
    '.',
    ''
);

$description = 'WordPress plan payment';
$type        = 'MERCHANT';
$reference   = 'ORDER-' . (int) ($_SESSION['post_id'] ?? 0) . '-' . time();

$first_name  = $current_user->display_name;
$last_name   = isset($_POST['last_name'])
    ? sanitize_text_field(wp_unslash($_POST['last_name']))
    : '';

$email       = sanitize_email($current_user->user_email);
$phonenumber = '';
$currency    = 'KES';

$callback_url = home_url('/pesapal/callback/');

if ($amount <= 0) {
    wp_die('Invalid payment amount.');
}

if ($email === '' && $phonenumber === '') {
    wp_die('An email address or phone number is required.');
}

$xml_attr = static function ($value) {
    return htmlspecialchars(
        (string) $value,
        ENT_QUOTES | ENT_XML1,
        'UTF-8'
    );
};

$post_xml = '<?xml version="1.0" encoding="utf-8"?>'
    . '<PesapalDirectOrderInfo'
    . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
    . ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"'
    . ' Amount="' . $xml_attr($amount) . '"'
    . ' Description="' . $xml_attr($description) . '"'
    . ' Type="' . $xml_attr($type) . '"'
    . ' Reference="' . $xml_attr($reference) . '"'
    . ' FirstName="' . $xml_attr($first_name) . '"'
    . ' LastName="' . $xml_attr($last_name) . '"'
    . ' Email="' . $xml_attr($email) . '"'
    . ' PhoneNumber="' . $xml_attr($phonenumber) . '"'
    . ' Currency="' . $xml_attr($currency) . '"'
    . ' xmlns="http://www.pesapal.com"'
    . ' />';

// 旧版 Pesapal PHP 集成示例通常要求进行 HTML 实体编码。
$post_xml = htmlentities($post_xml, ENT_QUOTES, 'UTF-8');

$consumer = new OAuthConsumer(
    $consumer_key,
    $consumer_secret
);

$iframe_src = OAuthRequest::from_consumer_and_token(
    $consumer,
    $token,
    'GET',
    $iframelink,
    $params
);

$iframe_src->set_parameter('oauth_callback', $callback_url);
$iframe_src->set_parameter('pesapal_request_data', $post_xml);

$iframe_src->sign_request(
    $signature_method,
    $consumer,
    $token
);
?>

接着输出完整的 iframe:

<iframe
    src="<?php echo esc_url($iframe_src); ?>"
    width="100%"
    height="620"
    scrolling="auto"
    frameborder="0">
    Unable to load the payment page
</iframe>

原代码末尾只显示了 iframe 的属性,没有看到完整的:

src="<?php echo ...; ?>"

如果实际页面中也缺少 src,即使 OAuth 请求没有问题,支付页面仍然无法加载。

XML 数据的关键要求

编码前生成的 XML 应与下面的结构类似:

<?xml version="1.0" encoding="utf-8"?>
<PesapalDirectOrderInfo
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    Amount="100.00"
    Description="WordPress plan payment"
    Type="MERCHANT"
    Reference="ORDER-1053-1234567890"
    FirstName="John"
    LastName="Doe"
    Email="john@example.com"
    PhoneNumber=""
    Currency="KES"
    xmlns="http://www.pesapal.com"
/>

请确认以下几点:

  • 根元素名称为 PesapalDirectOrderInfo。
  • XML 命名空间为 http://www.pesapal.com。
  • Amount 是不含逗号的正数。
  • Reference 不为空,并且保持唯一。
  • Type 使用接口支持的值,例如 MERCHANT。
  • Email 和 PhoneNumber 至少有一个有效值。
  • 用户输入中的 &、"、<、> 等字符已经过 XML 转义。
  • 不要对 XML、OAuth 参数和最终 URL 重复执行 urlencode(),OAuth 库通常会处理参数编码。

如果仍然出现 oauth_parameters_rejected

可以依次检查:

  1. 临时记录编码前的 XML,确认内容不是空白,并且可以正常解析。
  2. 检查 Consumer Key 和 Consumer Secret 是否填反。
  3. 确认测试凭据与 demo.pesapal.com 测试端点匹配。
  4. 检查服务器时间是否准确。OAuth 时间戳偏差过大也可能使签名失败。
  5. 确认没有在 sign_request() 之后新增或修改 OAuth 参数。
  6. 确认回调地址在签名前后完全一致。
  7. 确认 Currency 是当前账户和接口环境支持的币种。
  8. 检查生成的 $iframe_src 是否包含 oauth_consumer_key、oauth_signature、oauth_timestamp、oauth_nonce、oauth_callback 和 pesapal_request_data。
  9. 不要混用测试端点和正式商户凭据。

这段代码首先要修复的不是 OAuth 算法,而是空的 $post_xml。传入结构完整且正确转义的 PesapalDirectOrderInfo 后,再检查凭据、环境和签名参数是否一致。

备注:内容仅供参考。