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
可以依次检查:
- 临时记录编码前的 XML,确认内容不是空白,并且可以正常解析。
- 检查 Consumer Key 和 Consumer Secret 是否填反。
- 确认测试凭据与
demo.pesapal.com测试端点匹配。 - 检查服务器时间是否准确。OAuth 时间戳偏差过大也可能使签名失败。
- 确认没有在
sign_request()之后新增或修改 OAuth 参数。 - 确认回调地址在签名前后完全一致。
- 确认
Currency是当前账户和接口环境支持的币种。 - 检查生成的
$iframe_src是否包含oauth_consumer_key、oauth_signature、oauth_timestamp、oauth_nonce、oauth_callback和pesapal_request_data。 - 不要混用测试端点和正式商户凭据。
这段代码首先要修复的不是 OAuth 算法,而是空的 $post_xml。传入结构完整且正确转义的 PesapalDirectOrderInfo 后,再检查凭据、环境和签名参数是否一致。
备注:内容仅供参考。
