feat(pay): 卡密兑换入口收敛到三个购买弹窗并对齐参考主题

- 移除余额卡片独立「卡密兑换」通用弹窗,兑换入口分布到充值/积分/会员三个弹窗的支付方式中
- 充值弹窗补齐卡密 logo 与仅卡密时的金额选择隐藏逻辑
- 积分购买弹窗 1:1 复刻参考主题 points-pay modal(商品档位/自定义/支付方式/立即支付),REST 改为 product+custom 契约
- 会员弹窗注入卡密渠道:视图模型透传 card_pass,前端渲染卡密盒并直兑 /pay/card-pass/redeem type=vip
- 后台卡密兑换拆分为余额/积分/会员三个独立开关,VIP 段旧会员卡密组清理
- REST type 改为必填(balance/vip/credits),三类型独立闸门
- 测试重写为 card-pass-modal-redeem 契约,321 项全部通过
This commit is contained in:
2026-09-18 12:25:18 +08:00
parent 74e43aaa76
commit b88eab0bfe
18 changed files with 746 additions and 472 deletions
+48 -42
View File
@@ -49,7 +49,7 @@ class ChargeCard {
'args' => [
'password' => ['type' => 'string', 'required' => true, 'minLength' => 1, 'maxLength' => 80, 'sanitize_callback' => 'sanitize_text_field'],
'card' => ['type' => 'string', 'default' => '', 'maxLength' => 60, 'sanitize_callback' => 'sanitize_text_field'],
'type' => ['type' => 'string', 'default' => '', 'enum' => ['', self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS]],
'type' => ['type' => 'string', 'required' => true, 'enum' => [self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS]],
],
]);
}
@@ -91,9 +91,10 @@ class ChargeCard {
}
$password = sanitize_text_field((string) ($params['password'] ?? ''));
$card = sanitize_text_field((string) ($params['card'] ?? ''));
// 弹窗各带类型(余额充值 / 积分购买 / 会员购买),不再自动识别。
$type = sanitize_key((string) ($params['type'] ?? ''));
if ($type !== '' && !in_array($type, [self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS], true)) {
$type = '';
if (!in_array($type, [self::TYPE_BALANCE, self::TYPE_VIP, self::TYPE_CREDITS], true)) {
return new \WP_Error('aksrt_card_type', __('卡密兑换类型无效', 'aksrt'), ['status' => 400]);
}
$operation_key = \AKSRT\Ledger\OperationKey::from_request($request, 'card-redeem', get_current_user_id());
@@ -114,9 +115,11 @@ class ChargeCard {
/**
* 前台卡密兑换开关视图。
*
* 唯一类型闸门:balance.enabled =「启用卡密兑换功能」总开关
* (后台 余额充值 → 卡密兑换),并与收款接口 card_pass 主开关共同生效;
* 会员/积分卡密不再有独立开关。积分卡额外要求积分系统可用(系统级依赖)。
* 三个独立类型闸门(后台 余额充值 → 卡密兑换):
* 「启用卡密兑换余额功能」「启用卡密兑换积分功能」「启用卡密兑换会员功能」,
* 并共同受收款接口 card_pass 主开关(任一开关开启即生效)约束;
* 积分卡额外要求积分系统可用(系统级依赖)。
* 各类型仅在自身开启时进入对应购买弹窗的支付方式。
*
* @return array<string,mixed>
*/
@@ -125,38 +128,33 @@ class ChargeCard {
$channel_on = !class_exists('\AKSRT\Settings\PaymentRuntime')
|| \AKSRT\Settings\PaymentRuntime::is_gateway_enabled('card_pass');
$master_on = false;
$balance = ['enabled' => false, 'desc' => '', 'only_password' => true];
$vip_only_password = true;
$credits_on = false;
$types = [
'balance' => ['enabled' => false, 'desc' => '', 'only_password' => false],
'credits' => ['enabled' => false, 'desc' => '', 'only_password' => false],
'vip' => ['enabled' => false, 'desc' => '', 'only_password' => true],
];
if ($channel_on && class_exists('\AKSRT\Settings\Settings')) {
if (method_exists('\AKSRT\Settings\Settings', 'get_payment_recharge_settings')) {
$recharge = \AKSRT\Settings\Settings::get_payment_recharge_settings();
$pc = is_array($recharge['pass_charge'] ?? null) ? $recharge['pass_charge'] : [];
$master_on = !empty($pc['enabled']);
$balance = [
'enabled' => $master_on,
'desc' => (string) ($pc['desc'] ?? ''),
'only_password' => !isset($pc['only_password']) || !empty($pc['only_password']),
];
$pass_charge = is_array($recharge['pass_charge'] ?? null) ? $recharge['pass_charge'] : [];
foreach (array_keys($types) as $type_key) {
$cfg = is_array($pass_charge[$type_key] ?? null) ? $pass_charge[$type_key] : [];
$types[$type_key] = [
'enabled' => !empty($cfg['enabled']),
'desc' => (string) ($cfg['desc'] ?? ''),
'only_password' => !empty($cfg['only_password']),
];
}
}
if (method_exists('\AKSRT\Settings\Settings', 'get_payment_vip_settings')) {
$vip_s = \AKSRT\Settings\Settings::get_payment_vip_settings();
$benefit = is_array($vip_s['benefit'] ?? null) ? $vip_s['benefit'] : [];
$vip_only_password = !isset($benefit['pass_charge_only_password']) || !empty($benefit['pass_charge_only_password']);
}
if ($master_on) {
// 积分卡随总开关生效,仅要求积分系统可用(系统级依赖,非独立开关)。
$credits_on = !method_exists('\AKSRT\Settings\Settings', 'credits_enabled') || \AKSRT\Settings\Settings::credits_enabled();
if ($types['credits']['enabled']) {
// 积分卡额外要求积分系统可用(系统级依赖,非独立开关)。
$types['credits']['enabled'] = !method_exists('\AKSRT\Settings\Settings', 'credits_enabled')
|| \AKSRT\Settings\Settings::credits_enabled();
}
}
return [
'balance' => $balance,
'vip' => ['only_password' => $vip_only_password],
'credits' => ['enabled' => $credits_on],
];
return $types;
}
/**
@@ -323,12 +321,7 @@ class ChargeCard {
// only_password: if feature requires card number and empty, fail.
$opts = $this->get_public_options();
$only_password = true;
if ($type === self::TYPE_BALANCE) {
$only_password = !empty($opts['balance']['only_password']);
} elseif ($type === self::TYPE_VIP) {
$only_password = !empty($opts['vip']['only_password']);
}
$only_password = !empty($opts[$type]['only_password']);
if (!$only_password && $card === '' && (string) ($row['card'] ?? '') !== '') {
return new \WP_Error('aksrt_card_need_no', __('请输入卡号', 'aksrt'), ['status' => 400]);
}
@@ -819,13 +812,26 @@ class ChargeCard {
*/
private function assert_type_enabled(string $type) {
$opts = $this->get_public_options();
// 唯一类型闸门:后台「余额充值 → 卡密兑换」总开关(已含收款接口 card_pass 主开关)。
if (empty($opts['balance']['enabled'])) {
return new \WP_Error('aksrt_card_disabled', __('卡密兑换功能未开启', 'aksrt'), ['status' => 403]);
}
// 积分卡额外要求积分系统可用(系统级依赖,非独立开关)。
// 积分卡:积分系统不可用优先提示(get_public_options 已把它折叠进 enabled)。
if ($type === self::TYPE_CREDITS && empty($opts['credits']['enabled'])) {
return new \WP_Error('aksrt_card_disabled', __('积分系统未开启,无法兑换积分卡密', 'aksrt'), ['status' => 403]);
$toggle_on = false;
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_payment_recharge_settings')) {
$recharge = \AKSRT\Settings\Settings::get_payment_recharge_settings();
$toggle_on = !empty($recharge['pass_charge']['credits']['enabled'] ?? false);
}
return new \WP_Error('aksrt_card_disabled', $toggle_on
? __('积分系统未开启,无法兑换积分卡密', 'aksrt')
: __('卡密兑换积分功能未开启', 'aksrt'), ['status' => 403]);
}
// 三个独立类型闸门:各类型受自己的「启用卡密兑换XX功能」开关控制
// (开关内部已含收款接口 card_pass 主开关判断)。
if (empty($opts[$type]['enabled'])) {
$messages = [
self::TYPE_BALANCE => __('卡密兑换余额功能未开启', 'aksrt'),
self::TYPE_CREDITS => __('卡密兑换积分功能未开启', 'aksrt'),
self::TYPE_VIP => __('卡密兑换会员功能未开启', 'aksrt'),
];
return new \WP_Error('aksrt_card_disabled', (string) ($messages[$type] ?? __('卡密兑换功能未开启', 'aksrt')), ['status' => 403]);
}
return true;
}