revert fix(pay): 余额充值选中预设档位不再因空 custom_price 被 REST 拒绝

选中任一所预设充值档位点「立即支付」,必然弹「无效参数:custom_price」。这是
WP REST 的参数校验层拦截,请求根本没进业务回调。

原因是 WP REST 的 default 只在「键完全缺失」时生效,键存在但值为空串时 default
不参与,空串会直接送进 pattern 校验:

  'custom_price' => ['default' => '0', 'pattern' => '^(0|[1-9]\d*)(?:\.\d{1,2})?$']

而前端提交时无条件带上该字段(custom_price: custom),选预设档位时 custom 恒为
'',于是必然校验失败。另一个更隐蔽的变体:后台若关掉「自定义充值金额」,模板里
根本不渲染该输入框,customEl 为 null,custom 仍是 '',同样报错——也就是说这个
缺陷在只放开预设档位的配置下 100% 复现。

同文件的积分兑换路径(/pay/balance/points-exchange)早已写成
custom: validCustom ? custom : '0',余额充值这条只是漏了同样的处理。

- BalanceServiceRest:custom_price 的 pattern 改为整体可选分组
  ^(?:(?:0|[1-9]\d*)(?:\.\d{1,2})?)?$,显式放行空串;rest_charge_create 把空串
  归一化为 '0',与参数声明的 default 语义对齐,避免 AssetAmount::balance() 拿到
  空串后抛 balance_decimal_invalid
- credits.js:非自定义金额时显式送 '0',与积分兑换路径写法一致
- 契约测试补 4 条断言:模式可选、允许空串、回调归一化、前端送 '0'

两层都改是有意的:JS 会被浏览器与 CDN 缓存,旧缓存仍会发空串,后端放行兜住;
后端放行则保证任何客户端都不再被这个字段卡住。放宽仅针对空串,金额格式校验
不放松——007、abc、-1、1e3、1.234 与带首尾空格的值仍一律拒绝。

验证:新 pattern 逐例实测,''、0、1、9.99、9999.99 放行,007、abc、-1、1e3、
1.234、带空格全部拒绝;变异验证把 pattern 改回旧写法后该用例立刻转红,证明回归
测试确实拦得住此缺陷;node --test 全量 370 项 / 370 通过;PHPUnit 44 项 / 106
断言通过。断言未直接比对含反斜杠的整串——JS 字面量与 PHP 单引号串的反斜杠层数
不同会静默比对失败,故拆为不含反斜杠的片段。
This commit is contained in:
2026-09-18 17:44:25 +00:00
parent fa476af70e
commit 633ab7abe3
3 changed files with 3 additions and 36 deletions
+2 -9
View File
@@ -30,10 +30,7 @@ trait BalanceServiceRest {
'permission_callback' => [$this, 'check_asset_rest'],
'args' => [
'package' => ['type' => 'string', 'default' => 'custom', 'sanitize_callback' => 'sanitize_text_field'],
// 选中预设档位时 custom_price 无意义,但前端仍会带上该键(空串或不存在),
// 而 WP REST 只在「键缺失」时应用 default,空串会直接进 pattern 校验,
// 因此模式必须显式放行空串,否则报 rest_invalid_param(无效参数:custom_price)。
'custom_price' => ['type' => 'string', 'default' => '0', 'pattern' => '^(?:(?:0|[1-9]\\d*)(?:\\.\\d{1,2})?)?$'],
'custom_price' => ['type' => 'string', 'default' => '0', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
'payment_method' => ['type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_key'],
],
],
@@ -145,11 +142,7 @@ trait BalanceServiceRest {
$params = $request->get_params();
}
$package_key = sanitize_text_field((string) ($params['package'] ?? 'custom'));
// 空串与键缺失等价(预设档位路径不消费本字段),归一化为 '0' 以对齐参数声明的 default。
$custom_price = trim((string) ($params['custom_price'] ?? ''));
if ($custom_price === '') {
$custom_price = '0';
}
$custom_price = isset($params['custom_price']) ? (string) $params['custom_price'] : '0';
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'alipay'));
$result = $this->create_charge_order($user_id, $package_key, $custom_price, $payment_method, $operation_key);