Files
wordpress-theme-aksrt/tests/widget-system/card-pass-universal-redeem.test.cjs
T
kate522 3481620ed0 feat(payment): 通用卡密兑换弹窗并修正核销订单实付与订单名称
- /user/balance/ 余额卡片入口改为通用「卡密兑换」弹窗:请求不带类型,
  后端按卡密自动识别余额/会员/积分;卡号输入恒展示且选填
- 后台「余额充值 → 卡密充值」语义更新为「卡密兑换」,开关更名
  「启用卡密兑换功能」(设置键不变),双源 schema 同步并重建 SPA
- 删除死代码:credits-vip.php 死文件、credits-balance.php 内联积分
  兑换块、/pay/card-pass/options 路由与 rest_options、labels 数组、
  asset_points_card_description/password_only 死字段及内联块专属 CSS
- 卡密核销订单实付恒 0:create_redeem_order 源头写 0,UserOrders
  对 card_pass 订单视图层归零覆盖历史行(不迁移数据)
- 订单名称按 note.meta 生成:卡密兑换-余额充值X / 卡密兑换-积分充值X /
  购入[会员档位名]X天(永久)
- 新增契约 card-pass-universal-redeem.test.cjs,收紧 user-assets-layout
  契约(禁 .uc-card-pass 根类回潮)
2026-09-17 19:04:47 +08:00

112 lines
6.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 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'\)/);
// 积分卡的说明/单密码配置只服务于已删内联块,后台 schema 不应再有。
assert.doesNotMatch(usersSchemaPhp, /asset_points_card_description|asset_points_card_password_only/);
assert.doesNotMatch(usersSchemaJs, /asset_points_card_description|asset_points_card_password_only/);
assert.doesNotMatch(chargeCard, /asset_points_card_description|asset_points_card_password_only/);
// 失去宿主的内联块样式不回潮(弹窗仍在用 __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('订单中心:卡密核销订单实付归零并按 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);
}
});