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
+58 -1
View File
@@ -146,6 +146,21 @@ trait BalanceServiceCharge {
$methods[] = is_array($m) ? $m : ['id' => $id, 'name' => $id];
}
}
// 卡密支付渠道:受「启用卡密兑换余额功能」开关控制(开关内部已含收款接口主开关)。
$pass_charge = is_array($cfg['pass_charge'] ?? null) ? $cfg['pass_charge'] : [];
$card_pass_on = false;
if (class_exists('\AKSRT\Payment\ChargeCard')) {
$card_opts = \AKSRT\Payment\ChargeCard::get_instance()->get_public_options();
$card_pass_on = !empty($card_opts['balance']['enabled']);
$pass_charge = [
'balance' => is_array($card_opts['balance'] ?? null) ? $card_opts['balance'] : $pass_charge,
'credits' => is_array($card_opts['credits'] ?? null) ? $card_opts['credits'] : [],
'vip' => is_array($card_opts['vip'] ?? null) ? $card_opts['vip'] : [],
];
}
if ($card_pass_on) {
$methods[] = ['id' => 'card_pass', 'name' => __('卡密', 'aksrt')];
}
return [
'enabled' => !empty($cfg['enabled']),
'desc' => (string) ($cfg['desc'] ?? ''),
@@ -160,7 +175,7 @@ trait BalanceServiceCharge {
'service_charge' => (int) ($cfg['transfer']['service_charge'] ?? 0),
'desc' => (string) ($cfg['transfer']['desc'] ?? ''),
],
'pass_charge' => is_array($cfg['pass_charge'] ?? null) ? $cfg['pass_charge'] : [],
'pass_charge' => $pass_charge,
];
}
@@ -206,6 +221,48 @@ trait BalanceServiceCharge {
/**
* 积分商品行解析("积分|支付|标签",参考主题 pay_points_product 同款结构)。
*
* @return array<int,array{key:string,points:int,pay_price:string,title:string,tag:string}>
*/
public function parse_points_packages(string $raw): array {
$out = [];
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
$i = 0;
foreach ($lines as $line) {
$line = trim((string) $line);
if ($line === '') {
continue;
}
$parts = array_map('trim', explode('|', $line));
if (count($parts) < 2) {
continue;
}
$points = (int) $parts[0];
try {
$pay = \AKSRT\Ledger\AssetAmount::balance((string) $parts[1]);
} catch (\InvalidArgumentException $e) {
continue;
}
if ($points <= 0 || $pay->minor() <= 0) {
continue;
}
$title = $parts[2] ?? '';
$out[] = [
'key' => (string) $i,
'points' => $points,
'pay_price' => $pay->decimal(),
'title' => $title !== '' ? $title : sprintf(__('购买 %d 积分', 'aksrt'), $points),
'tag' => $title,
];
$i++;
}
return $out;
}
/**
* @return array<string,mixed>|\WP_Error
*/