Files
wordpress-theme-aksrt/tests/widget-system/card-pass-universal-redeem.test.cjs
T
kate522 64ed47165d fix(payment): 卡密兑换开关收敛为唯一总开关
- 问题:积分卡密兑换报「积分卡密兑换未开启」,因 asset_points_card_enabled
  与总开关并存为双重闸门;会员卡同样存在隐藏的第二开关 pay_vip_pass_charge_s。
- 修复:ChargeCard 类型闸门统一只消费后台「余额充值 → 卡密兑换」总开关;
  积分卡仅额外要求积分系统可用(系统级依赖,非独立开关)。
- 死代码清理:PaymentRuntime 删除 benefit.pass_charge/pass_charge_desc 映射;
  后台 schema 删除「卡密兑换会员」独立开关组与「启用积分卡密兑换」开关(双源同步);
  vip options 收敛为仅 only_password 展示配置。
- 验证:php -l 4 文件无错;契约测试 59/59 通过;容器内探针实测
  credits 类型闸门放行;SPA 已重建并同步运行副本。
2026-09-17 19:18:54 +08:00

129 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 通用卡密兑换契约(/user/balance/ 余额卡片弹窗)。
*
* 背景:余额卡片右下角入口原为「卡密兑换余额」专用弹窗(强制 data-type="balance")。
* 现改为通用「卡密兑换」弹窗:标题、文案与提交不再绑定余额类型,
* 请求不带类型,由后端 ChargeCard::redeem_for_user() 按卡密自动识别
* (balance / vip / credits),各类型仍受自身开关与收款接口 card_pass 主开关约束。
* 前台只保留这一个兑换入口:页内联兑换块(积分/会员)与死文件 credits-vip.php
* 已删除,会员/积分卡密统一经通用弹窗兑换。
*
* 后台语义:余额充值 → 卡密兑换 → 「启用卡密兑换功能」
* (原「启用卡密充值余额功能」,设置键不变,仅语义/文案更新)。
*
* 卡密核销订单契约:核销不发生真实支付(现金在售卡时已确认),
* 订单金额恒为 0(create_redeem_order 源头 + UserOrders 视图层归零覆盖历史行,
* 不做数据迁移);订单名称按 note.meta 生成:
* 卡密兑换-余额充值X / 卡密兑换-积分充值X / 购入[会员档位名]X天(永久)。
*/
const test = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const root = path.join(__dirname, '..', '..');
const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8');
const wallet = read('inc', 'helpers', 'user-center-renderers', 'credits-wallet.php');
const creditsJs = read('assets', 'js', 'credits.js');
const chargeCard = read('src', 'Payment', 'ChargeCard.php');
const userOrders = read('src', 'Payment', 'UserOrders.php');
const schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
const schemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'recharge.js');
const usersSchemaPhp = read('src', 'Admin', 'Schema', 'SchemaUsers.php');
const usersSchemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'users', 'credits.js');
const vipSchemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'vip.js');
const creditsRenderer = read('inc', 'helpers', 'user-center-renderers', 'credits-balance.php');
const creditsCss = read('assets', 'css', 'routes', 'user-center', 'credits.css');
test('余额卡片弹窗为通用卡密兑换:标题与提交不再绑定余额类型', () => {
assert.match(wallet, /esc_html_e\('卡密兑换', 'aksrt'\)/);
assert.doesNotMatch(wallet, /卡密兑换余额/);
assert.doesNotMatch(wallet, /data-type="balance"/);
assert.doesNotMatch(wallet, /esc_html__\('兑换余额', 'aksrt'\)/);
// 弹窗根节点不带类型值 → 前端 type='' → 后端按卡密自动识别。
assert.match(wallet, /data-aksrt-card-pass-modal data-aksrt-card-pass=""/);
assert.match(wallet, /data-aksrt-card-redeem(?![^>]*data-type)/);
// 卡号输入恒展示且为选填(是否必填由后端按卡密类型判定)。
assert.match(wallet, /data-aksrt-card-no(?![^>]*required)/);
assert.match(wallet, /请输入卡号(选填)/);
});
test('入口展示受「启用卡密兑换功能」总开关控制', () => {
assert.match(wallet, /\$card_pass_redeem\['enabled'\]/);
assert.doesNotMatch(wallet, /\$card_pass_balance/);
// ChargeCard 缺失时不做 pass_charge 兜底(兑换 REST 依赖该类,兜底无意义)。
assert.doesNotMatch(wallet, /\$recharge\['pass_charge'\]/);
});
test('前台唯一兑换入口:页内联块与死文件已删除,JS 不再有 required 卡号校验', () => {
assert.equal(
fs.existsSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', 'credits-vip.php')),
false,
'credits-vip.php 死文件应已删除'
);
assert.doesNotMatch(creditsRenderer, /data-aksrt-card-pass|data-aksrt-card-redeem|card_pass_credits/);
assert.doesNotMatch(creditsJs, /hasAttribute\('required'\)/);
// 会员/积分卡密无独立开关与说明/单密码配置残留。
assert.doesNotMatch(usersSchemaPhp, /asset_points_card_/);
assert.doesNotMatch(usersSchemaJs, /asset_points_card_/);
assert.doesNotMatch(chargeCard, /asset_points_card_/);
assert.doesNotMatch(chargeCard, /'pass_charge_desc'/);
// 失去宿主的内联块样式不回潮(弹窗仍在用 __input/__btn/__tip 子元素样式)。
assert.doesNotMatch(creditsCss, /^\.uc-card-pass\s*\{/m);
assert.doesNotMatch(creditsCss, /uc-card-pass__title|uc-card-pass__desc|\.uc-assets-col > \.uc-card-pass/);
});
test('前端:VIP 缓存失效按后端识别类型', () => {
assert.match(creditsJs, /var redeemedType = \(data && data\.type\) \|\| type;/);
assert.match(creditsJs, /redeemedType === 'vip'/);
});
test('后端:核销订单金额恒为 0,options 路由与 labels 死代码不回潮', () => {
assert.match(chargeCard, /\/pay\/card-pass\/redeem/);
assert.doesNotMatch(chargeCard, /card-pass\/options/);
assert.doesNotMatch(chargeCard, /rest_options/);
assert.doesNotMatch(chargeCard, /'labels'\s+=>\s+\$labels/);
// 通用兑换能力后端契约保持:三类可兑换 + 空类型自动识别。
assert.match(chargeCard, /REDEEMABLE_TYPES = \[self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS\]/);
assert.match(chargeCard, /'enum' => \['', self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS\]/);
// create_redeem_order:余额卡不再把面额写进订单金额。
assert.doesNotMatch(chargeCard, /TYPE_BALANCE\)\s*\{[^}]*parsed_amount/s);
assert.match(chargeCard, /订单金额恒为 0/);
// 积分卡开关仅作为后端类型闸门。
assert.match(chargeCard, /'credits' => \['enabled' => /);
});
test('唯一类型闸门:总开关统一约束三类卡密,独立开关不回潮', () => {
// assert_type_enabled 只查总开关(+ 积分系统级依赖),不再有按类型的独立开关。
assert.match(chargeCard, /卡密兑换功能未开启/);
assert.match(chargeCard, /积分系统未开启/);
assert.doesNotMatch(chargeCard, /余额卡密兑换未开启|会员卡密兑换未开启|积分卡密兑换未开启/);
// 会员/积分卡密独立开关与说明为死代码。
assert.doesNotMatch(chargeCard, /pay_vip_pass_charge_s|pay_vip_pass_charge_desc|pass_charge'\s*=>/);
for (const [name, src] of [['SchemaPayment.php', schemaPhp], ['recharge.js', schemaJs], ['vip.js', vipSchemaJs]]) {
assert.doesNotMatch(src, /pay_vip_pass_charge_s|pay_vip_pass_charge_desc|启用卡密兑换会员功能/, name);
assert.doesNotMatch(src, /启用积分卡密兑换/, name);
}
assert.match(vipSchemaJs, /title: '会员卡密'/);
assert.match(vipSchemaJs, /pay_vip_pass_charge_only_password/);
});
test('订单中心:卡密核销订单实付归零并按 note 生成订单名称', () => {
assert.match(userOrders, /if \(\$payment_method === 'card_pass'\)\s*\{\s*\$amount = \$is_points \? '0' : '0\.00';/);
assert.match(userOrders, /if \(!empty\(\$meta\['card_pass'\]\)\)\s*\{[\s\S]*?resolve_card_pass_title\(\$meta\)/);
assert.match(userOrders, /卡密兑换-余额充值/);
assert.match(userOrders, /卡密兑换-积分充值/);
assert.match(userOrders, /购入\[%1\$s\]%2\$s/);
assert.match(userOrders, /永久/);
});
test('后台 schema 语义更新为「启用卡密兑换功能」', () => {
for (const [name, src] of [['SchemaPayment.php', schemaPhp], ['recharge.js', schemaJs]]) {
assert.match(src, /启用卡密兑换功能/, name);
assert.match(src, /title['"]?\s*(?::|=>)\s*'卡密兑换'/, name);
assert.doesNotMatch(src, /启用卡密充值余额功能/, name);
assert.doesNotMatch(src, /卡密充值说明/, name);
}
});