fix(pay): 余额充值预设档位不再被 custom_price 参数校验拦截
选中任一预设充值档位点「立即支付」,必然弹「无效参数:custom_price」。这是 WP
REST 的参数校验层拦截,请求根本没进业务回调。
原因是 WP REST 的 default 只在「键完全缺失」时生效,键存在但值为空串时 default
不参与,空串会直接送进 pattern 校验:
'custom_price' => ['type' => 'string', 'default' => '0', 'pattern' => '^(0|[1-9]\d*)(?:\.\d{1,2})?$']
而前端提交时无条件带上该字段(custom_price: custom),选预设档位时 custom 恒为
''。更隐蔽的变体:后台关掉「自定义充值金额」后模板不再渲染该输入框,customEl 为
null、custom 仍是 '',也就是只放开预设档位的配置下 100% 复现。
该字段只在 package=custom 时被消费,而金额格式与区间的业务裁决早已完整存在于
create_charge_order()(AssetAmount::balance 严格正则 + 溢出检查,再按 custom_limit
校验区间并返回本地化错误)。参数声明上的 pattern 是同一规则的第二份实现,两套口径
不同步即是本次缺陷来源。故本次按「单一口径」收口,不再采用「放宽 pattern + 回调归一化
空串 + 前端改送 '0'」的三处并行写法:
- BalanceServiceRest:custom_price 参数去掉 pattern、default 改为 ''、显式声明
sanitize_callback;rest_charge_create 改为经声明式 args 用 $request->get_param()
取值,不再手工重读 JSON 体
- credits.js:selectProduct 选中预设档位时清空自定义金额输入(否则框内残留数字会在
提交时把 package 顶回 custom,静默覆盖用户刚点的档位);customInput 声明上移,去掉
「先调用后赋值」的时序依赖
- balance-charge-modal.test.cjs:新增 2 项契约测试 / 6 条断言
清理的死代码:
- rest_charge_create 中手工 get_json_params() 并复刻 sanitize_text_field/sanitize_key
的一段:与声明的 sanitize_callback 重复,使那些声明形同虚设(实为第二套取值路径)
- credits.js 提交处理器内重复读取 payment_method 的死分支(处理器开头已读取,中间无
任何代码改动它)
放宽仅限「空串 / 键缺失」这一种情形:package=custom 而金额为空、非法或越界仍一律
400,007、abc、1e3、1.234、负数等仍被 AssetAmount::balance() 拒绝,失败方向仍是收紧。
行为变化(需知悉):
- 金额裁决权归 AssetAmount::balance(),其内部 trim() 使带首尾空格的金额被修剪后接受,
此前被参数层 pattern 直接拒绝。这是「单一裁决者」的必然结果。
- 点选预设档位会清空自定义金额输入框,是修复该档位被静默覆盖的必要行为。
验证:node --test 全量 372 项 / 372 通过(原 370 项,新增 2 项);PHPUnit 44 项 /
106 断言通过;改动 PHP 文件 php -l 通过。变异验证:把 6 条关键断言跑在修复前的文件
内容上,6/6 转红,证明回归测试拦得住此缺陷。未做视觉验收,弹窗标记与 CSS 未改动。
This commit is contained in:
@@ -30,7 +30,13 @@ trait BalanceServiceRest {
|
||||
'permission_callback' => [$this, 'check_asset_rest'],
|
||||
'args' => [
|
||||
'package' => ['type' => 'string', 'default' => 'custom', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'custom_price' => ['type' => 'string', 'default' => '0', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
|
||||
// 仅 package=custom 时被消费;选预设档位时前端送空串,空串即
|
||||
//「未使用自定义金额」,属正常取值而非非法值。金额格式与区间的
|
||||
// 唯一裁决在 create_charge_order()(AssetAmount::balance + custom_limit),
|
||||
// 这里再挂一份 pattern 会形成第二套口径:WP 的 default 只在键缺失时
|
||||
// 生效,键存在而值为空串时 pattern 会直接判失败,把预设档位误报为
|
||||
// rest_invalid_param(无效参数:custom_price)。
|
||||
'custom_price' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'payment_method' => ['type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_key'],
|
||||
],
|
||||
],
|
||||
@@ -137,13 +143,11 @@ trait BalanceServiceRest {
|
||||
if (is_wp_error($operation_key)) {
|
||||
return $operation_key;
|
||||
}
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
$package_key = sanitize_text_field((string) ($params['package'] ?? 'custom'));
|
||||
$custom_price = isset($params['custom_price']) ? (string) $params['custom_price'] : '0';
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'alipay'));
|
||||
// 单一取值口径:直接读注册时声明的 args(type / default / sanitize_callback
|
||||
// 均已声明),不再手工重读 JSON 体并复刻一遍清洗逻辑。
|
||||
$package_key = (string) $request->get_param('package');
|
||||
$custom_price = (string) $request->get_param('custom_price');
|
||||
$payment_method = (string) $request->get_param('payment_method');
|
||||
|
||||
$result = $this->create_charge_order($user_id, $package_key, $custom_price, $payment_method, $operation_key);
|
||||
if (is_wp_error($result)) {
|
||||
|
||||
Reference in New Issue
Block a user