revert refactor(pay): 充值商品以充值金额为唯一真值,移除销售金额与折扣 充值商品行此前是双值模型:price 充值金额决定到账、pay_price 销售金额决定支付, pay_price 为 0 时回落到 price,否则即「付更少拿更多」的折扣。该模型把三处耦合在 一起——后台 schema 暴露销售金额输入、线格式首段携带支付额、下单时计价与到账分离。 只改其中一处就会出现「后台不再可配但前端仍在展示折扣」或「展示已合并但下单仍按 旧价扣款」的静默不一致,故本次三层一并收口,充值金额即实际支付金额。 后台 schema 有两份且必须同步:SPA 侧 recharge.js 负责渲染表单,PHP 侧 SchemaPayment 负责默认值与清洗。只改一份会让界面不再可配而默认值仍在写 pay_price。 线格式三段 "支付|到账|标题" 保持不动,仅为兼容历史已存的字符串配置:支付段不再读取 任何配置来源,恒等于充值金额,故历史配置里的折扣价会被忽略。旧数据无需迁移,pay_price 键留在库中只是不再被读取,从重建后的后台保存一次即自然消失。 行为变化(需知悉): - 已配置折扣的站点,其用户将按充值金额全额支付,即对这些用户是涨价。这是「不做优惠 和促销」的必然结果。 - parse_packages 的校验口径随之变松:有效性只由到账段决定,故 "abc|10|x" 这类首段 非法、到账段合法的行会被解析为 10 元档,而旧实现两段都作金额校验、会丢弃。理由是 支付段已不表达任何含义,因一个无用字段是垃圾就丢弃一条本来有效的档位只会让运营困惑。 既有数据不受影响——旧格式化器写出的首段恒为数字,只有手改出的脏数据才可能命中。 该取舍已写入方法文档。 - recharge.js / SchemaPayment:删除 pay_price 字段与三档默认值中的该键,help 去掉 「同时支持设置折扣」 - PaymentRuntime::format_product_lines:不再读取 pay_price,支付段由充值金额充当 - BalanceServiceCharge::parse_packages:不再产出 pay_price 键,金额唯一来源为到账段 - BalanceServiceCharge::create_charge_order:支付额与到账额同源于 price - credits-wallet.php:移除销售金额行、「省X」折扣徽标及仅服务折扣的差额计算 - BalanceServiceRest:仅注释改写,说明积分路径的 pay_price 是售价而非折扣 - 新增 tests/widget-system/recharge-product-price-contract.test.cjs:5 条跨层契约 范围边界:积分商品 pay_points_product 的 pay_price 是「积分|支付|标签」里的实际售价, 本身不含折扣语义,保留不动。契约测试以正向断言固定该边界,避免日后被误当作同类问题 一并删掉。 验证:新增契约测试 5 项通过;node --test 全量 375 项 / 375 通过(原 370 项);PHPUnit 44 项 / 106 断言通过;改动的 PHP 文件 php -l 全部通过。变异验证:把 create_charge_order 改回按 pay_price 计价、把 pay_price 重新注入模板,对应断言分别转红,证明回归测试拦得 住回退。另以反射调用真实代码做端到端执行验证 11 项全过,覆盖结构化数组 -> 线格式 -> 商品数组全链路、数组内残存 pay_price 被忽略、历史折扣字符串(98|100|旧折扣档 取 100)、 到账段为 0 或非法时跳过等边界。 部署提示:assets/admin/build 为部署时构建、未入库,后台表单的改动需在目标机执行 cd assets/admin && npm ci && npm run build 后才会生效。前端 credits.js 走 filemtime 版本号,无需手工清缓存。
250 lines
14 KiB
PHP
250 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* User center: cash wallet (站内余额) — legacy-like asset card.
|
|
*
|
|
* Expects extracted $context vars.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
if (empty($balance_enabled)) {
|
|
return;
|
|
}
|
|
|
|
$normalize_balance = static function ($value): \AKSRT\Ledger\AssetAmount {
|
|
try {
|
|
return \AKSRT\Ledger\AssetAmount::balance(is_string($value) ? $value : '');
|
|
} catch (\InvalidArgumentException $e) {
|
|
return \AKSRT\Ledger\AssetAmount::balance('0');
|
|
}
|
|
};
|
|
$wallet = $normalize_balance($wallet_balance ?? '0');
|
|
$wallet_fmt = $wallet->formatted();
|
|
|
|
$recharge = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_payment_recharge_settings')
|
|
? \AKSRT\Settings\Settings::get_payment_recharge_settings()
|
|
: [];
|
|
$uid = (int) ($user_id ?? get_current_user_id());
|
|
$transfer_on = !empty($recharge['transfer']['enabled']);
|
|
if ($transfer_on && class_exists('\AKSRT\Settings\Capabilities') && !\AKSRT\Settings\Capabilities::user_can('balance_transfer', $uid)) {
|
|
$transfer_on = false;
|
|
}
|
|
$all_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
|
$withdraw_on = !empty($all_settings['pay_balance_withdraw_s']);
|
|
if ($withdraw_on && class_exists('\AKSRT\Settings\Capabilities') && !\AKSRT\Settings\Capabilities::user_can('balance_withdraw', $uid)) {
|
|
$withdraw_on = false;
|
|
}
|
|
$withdraw_settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_withdrawal_settings() : [];
|
|
$withdraw_min = $normalize_balance($withdraw_settings['min_amount'] ?? '50')->decimal();
|
|
$withdraw_usage = ['remaining_count' => 0, 'remaining_amount' => '0.00'];
|
|
$withdraw_pending_hold = '0.00';
|
|
if ($withdraw_on && class_exists('\AKSRT\Payment\Withdrawal')) {
|
|
$wd_svc = \AKSRT\Payment\Withdrawal::get_instance();
|
|
$withdraw_pending_hold = $wd_svc->sum_pending_hold((int) ($user_id ?? 0));
|
|
$withdraw_usage = $wd_svc->daily_usage((int) ($user_id ?? 0), $withdraw_settings);
|
|
}
|
|
// 卡密支付渠道(后台 余额充值 → 卡密兑换 → 启用卡密兑换余额功能):
|
|
// 兑换入口收敛到充值弹窗的支付方式,不再有独立入口。
|
|
$card_pass_redeem = ['enabled' => false, 'desc' => '', 'only_password' => false];
|
|
if (class_exists('\AKSRT\Payment\ChargeCard')) {
|
|
$card_opts = \AKSRT\Payment\ChargeCard::get_instance()->get_public_options();
|
|
$card_pass_redeem = is_array($card_opts['balance'] ?? null) ? $card_opts['balance'] : $card_pass_redeem;
|
|
}
|
|
|
|
$charge_view = [];
|
|
if (class_exists('\AKSRT\Credits\BalanceService')) {
|
|
$charge_view = \AKSRT\Credits\BalanceService::get_instance()->get_charge_view((int) $user_id);
|
|
}
|
|
$packages = is_array($charge_view['packages'] ?? null) ? $charge_view['packages'] : [];
|
|
$methods = is_array($charge_view['payment_methods'] ?? null) ? $charge_view['payment_methods'] : [];
|
|
$custom_s = !empty($charge_view['custom_s']);
|
|
$custom_limit = (int) ($charge_view['custom_limit'] ?? 9999);
|
|
$mark = (string) ($charge_view['currency_mark'] ?? '元');
|
|
?>
|
|
<div class="uc-asset-card uc-asset-card--wallet">
|
|
<div class="uc-asset-card__head flex jsb ac">
|
|
<div class="uc-asset-card__title">
|
|
<i class="fa fas fa-money-bill em12 mr6" aria-hidden="true"></i><?php esc_html_e('余额', 'aksrt'); ?>
|
|
</div>
|
|
<?php if ($withdraw_on) : ?>
|
|
<button type="button" class="uc-asset-card__head-action muted-color px12" data-aksrt-withdraw-history-open>
|
|
<?php esc_html_e('提现记录', 'aksrt'); ?>
|
|
<i class="fa fas fa-angle-right em12 ml3" aria-hidden="true"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="uc-asset-card__body flex jsb ac">
|
|
<div class="uc-asset-card__amount c-blue-2">
|
|
<?php if (!in_array($mark, ['', '元', '¥'], true)) : ?>
|
|
<span class="uc-asset-card__mark px12"><?php echo esc_html($mark); ?></span>
|
|
<?php endif; ?>
|
|
<span class="uc-asset-card__value font-bold" data-aksrt-wallet-value title="<?php echo esc_attr($wallet_fmt); ?>"><?php echo esc_html($wallet_fmt); ?></span>
|
|
</div>
|
|
<div class="uc-asset-card__actions flex0">
|
|
<?php if ($transfer_on) : ?>
|
|
<button type="button" class="but c-blue-2 mr6 hollow" data-aksrt-transfer-open="balance"><?php esc_html_e('转账', 'aksrt'); ?></button>
|
|
<?php endif; ?>
|
|
<?php if ($withdraw_on) : ?>
|
|
<button type="button" class="but c-blue-2 mr6 hollow" data-aksrt-withdraw-open><?php esc_html_e('提现', 'aksrt'); ?></button>
|
|
<?php endif; ?>
|
|
<button type="button" class="but b-blue-2" data-aksrt-charge-open><?php esc_html_e('充值', 'aksrt'); ?></button>
|
|
</div>
|
|
</div>
|
|
<?php if ($withdraw_on) : ?>
|
|
<div class="uc-asset-card__foot muted-2-color em09 mt6 flex jsb ac">
|
|
<span><?php printf(esc_html__('冻结中 %s', 'aksrt'), esc_html($normalize_balance($withdraw_pending_hold)->formatted())); ?></span>
|
|
<span></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Charge modal — Zibll balance-charge modal 1:1 (refresh_modal shell + zibpay_get_balance_charge_modal) -->
|
|
<?php
|
|
$charge_method_logos = [
|
|
'balance' => 'pay-balance-logo.svg',
|
|
'alipay' => 'pay-alipay-logo.svg',
|
|
'epay_alipay' => 'pay-alipay-logo.svg',
|
|
'wechatpay' => 'pay-wechat-logo.svg',
|
|
'epay_wxpay' => 'pay-wechat-logo.svg',
|
|
'paypal' => 'pay-paypal-logo.svg',
|
|
'stripe' => 'pay-stripe-logo.svg',
|
|
'card_pass' => 'pay-card-pass-logo.svg',
|
|
];
|
|
$charge_img_uri = get_template_directory_uri() . '/assets/img/';
|
|
$charge_default_product = $packages ? (string) $packages[0]['key'] : 'custom';
|
|
// 仅卡密可用时隐藏金额选择(参考主题:卡密决定充值金额)。
|
|
$charge_card_only = count($methods) === 1 && (string) ($methods[0]['id'] ?? '') === 'card_pass';
|
|
// 卡密输入盒是 payment_method 条件块,与它在渠道行中的位置无关:卡密渠道可由
|
|
// 「收款接口显示顺序」移到任意位置,故此处只判断渠道是否存在。
|
|
$charge_card_in_methods = in_array('card_pass', array_column($methods, 'id'), true);
|
|
$charge_mark_symbol = !in_array($mark, ['', '元'], true);
|
|
$charge_mark_html = $charge_mark_symbol
|
|
? '<span class="px12">' . esc_html($mark) . '</span>'
|
|
: '';
|
|
$charge_unit = $charge_mark_symbol ? '' : (string) $mark;
|
|
// 与 Zibll 格式对齐(mark 前缀:最低充值¥1);mark 配置为单位(元)时用后缀:最低充值1元。
|
|
$charge_limit_html = $charge_mark_symbol
|
|
? sprintf(__('最低充值%1$s1,最高充值%1$s%2$s', 'aksrt'), $mark, $custom_limit)
|
|
: sprintf(__('最低充值1%1$s,最高充值%2$s%1$s', 'aksrt'), $charge_unit, $custom_limit);
|
|
$charge_custom_block = static function () use ($mark, $charge_mark_html, $charge_unit, $charge_limit_html, $custom_limit) {
|
|
?>
|
|
<div data-for="balance_product" data-value="custom">
|
|
<div class="relative flex ab">
|
|
<span class="ml6 mr10 muted-color shrink0"><?php echo esc_html($mark); ?></span>
|
|
<input class="line-form-input em16 key-color" style="padding: 1px;" name="custom_price" type="number" limit-min="1" limit-max="<?php echo esc_attr((string) $custom_limit); ?>" placeholder="<?php echo esc_attr('1 ~ ' . $custom_limit); ?>">
|
|
<i class="line-form-line"></i>
|
|
</div>
|
|
<div class="muted-2-color em09 mt6"><?php echo esc_html($charge_limit_html); ?></div>
|
|
</div>
|
|
<?php
|
|
};
|
|
?>
|
|
<div class="modal flex jc fade" id="aksrt-balance-charge-modal" tabindex="-1" role="dialog" data-aksrt-charge-modal>
|
|
<div class="modal-dialog modal-mini full-sm" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-body">
|
|
<div class="mb10 touch"><button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button><b class="modal-title flex ac"><span class="mr6 em14"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-money-color-2"></use></svg></span><?php esc_html_e('余额充值', 'aksrt'); ?></b></div>
|
|
<form class="balance-charge-form mini-scrollbar scroll-y max-vh7" data-aksrt-charge-form>
|
|
<?php if (!$charge_card_only) : ?>
|
|
<?php if ($packages) : ?>
|
|
<div class="charge-box mb20">
|
|
<div class="muted-color mb6"><?php esc_html_e('请选择充值金额', 'aksrt'); ?></div>
|
|
<?php foreach ($packages as $idx => $pkg) :
|
|
$package_credit = $normalize_balance($pkg['price'] ?? '');
|
|
$package_pay = $normalize_balance($pkg['pay_price'] ?? '');
|
|
$package_saving_minor = max(0, $package_credit->minor() - $package_pay->minor());
|
|
?>
|
|
<div class="zib-widget vip-product relative product-box<?php echo $idx === 0 ? ' active' : ''; ?>" data-for="balance_product" data-value="<?php echo esc_attr((string) $pkg['key']); ?>">
|
|
<?php if (!empty($pkg['tag'])) : ?>
|
|
<div class="abs-right vip-tag badg jb-yellow"><?php echo esc_html((string) $pkg['tag']); ?></div>
|
|
<?php endif; ?>
|
|
<div class="em14"><?php echo $charge_mark_html; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped span + esc_html mark above ?><?php echo esc_html((string) $pkg['price']); ?></div>
|
|
<div class="c-red"><?php echo $charge_mark_html; // phpcs:ignore WordPress.Security.EscapeOutput ?><span><?php echo esc_html((string) $pkg['pay_price']); ?></span><?php if ($package_saving_minor > 0) : ?><span class="ml6 c-yellow smail"><?php printf(esc_html__('省%s', 'aksrt'), esc_html(\AKSRT\Ledger\AssetAmount::decimal_from_minor($package_saving_minor) . $charge_unit)); ?></span><?php endif; ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php if ($custom_s) : ?>
|
|
<div class="muted-color mt20 mb6"><?php esc_html_e('自定义充值金额', 'aksrt'); ?></div>
|
|
<?php $charge_custom_block(); ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php elseif ($custom_s) : ?>
|
|
<div class="charge-box mb20">
|
|
<div class="muted-color mb6"><?php esc_html_e('请输入充值金额', 'aksrt'); ?></div>
|
|
<?php $charge_custom_block(); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
<?php if (!empty($charge_view['desc'])) : ?>
|
|
<div class="muted-box muted-2-color padding-10 mb10 em09"><?php echo esc_html((string) $charge_view['desc']); ?></div>
|
|
<?php endif; ?>
|
|
<div class="dependency-box">
|
|
<?php if (count($methods) > 1) : ?>
|
|
<div class="muted-2-color em09 mb6"><?php esc_html_e('请选择支付方式', 'aksrt'); ?></div>
|
|
<div class="flex mb10">
|
|
<?php foreach ($methods as $mi => $m) :
|
|
$method_id = (string) ($m['id'] ?? '');
|
|
$method_logo = $charge_method_logos[$method_id] ?? '';
|
|
?>
|
|
<div class="flex jc hh payment-method-radio hollow-radio flex-auto pointer<?php echo $mi === 0 ? ' active' : ''; ?>" data-for="payment_method" data-value="<?php echo esc_attr($method_id); ?>">
|
|
<?php if ($method_logo !== '') : ?>
|
|
<img src="<?php echo esc_url($charge_img_uri . $method_logo); ?>" alt="<?php echo esc_attr($method_id); ?>-logo">
|
|
<?php endif; ?>
|
|
<div><?php echo esc_html((string) ($m['name'] ?? $method_id)); ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if ($charge_card_in_methods) : ?>
|
|
<div class="mb10 padding-h10 padding-w6" data-controller="payment_method" data-condition="==" data-value="card_pass"<?php echo !$charge_card_only ? ' style="display: none;"' : ''; ?>>
|
|
<?php if (!empty($card_pass_redeem['desc'])) : ?>
|
|
<div class="muted-box muted-2-color padding-10 mb10 em09"><?php echo esc_html((string) $card_pass_redeem['desc']); ?></div>
|
|
<?php endif; ?>
|
|
<div class="muted-2-color em09 mb6"><?php echo !empty($card_pass_redeem['only_password']) ? esc_html__('请输入卡密', 'aksrt') : esc_html__('请输入卡号和密码', 'aksrt'); ?></div>
|
|
<?php if (empty($card_pass_redeem['only_password'])) : ?>
|
|
<div class="mb6"><input type="input" class="form-control" name="card_pass[card]" placeholder="<?php esc_attr_e('卡号', 'aksrt'); ?>" value=""></div>
|
|
<?php endif; ?>
|
|
<div class="">
|
|
<input type="input" class="form-control" name="card_pass[password]" placeholder="<?php esc_attr_e('卡密', 'aksrt'); ?>" value="">
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<input type="hidden" name="balance_product" value="<?php echo esc_attr($charge_default_product); ?>">
|
|
<input type="hidden" name="payment_method" value="<?php echo esc_attr((string) ($methods[0]['id'] ?? '')); ?>">
|
|
<?php if ($methods) : ?>
|
|
<button type="submit" class="mt6 but jb-red initiate-pay btn-block radius"><?php esc_html_e('立即支付', 'aksrt'); ?></button>
|
|
<?php else : ?>
|
|
<span class="badg px12 c-yellow-2"><?php esc_html_e('暂时无法支付,请与客服联系', 'aksrt'); ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($withdraw_on) :
|
|
$withdraw_modal_rendered = !empty($GLOBALS['aksrt_withdraw_modal_rendered']);
|
|
if (!$withdraw_modal_rendered) {
|
|
require __DIR__ . '/withdraw-modal.php';
|
|
}
|
|
endif; ?>
|
|
|
|
|
|
<?php if ($withdraw_on) : ?>
|
|
<div class="uc-modal" id="aksrt-withdraw-history-modal" hidden data-aksrt-withdraw-history-modal>
|
|
<div class="uc-modal__overlay" data-aksrt-withdraw-history-close></div>
|
|
<div class="uc-modal__dialog uc-withdraw-history-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="aksrt-withdraw-history-title">
|
|
<button type="button" class="uc-modal__close" data-aksrt-withdraw-history-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><i class="fa fas fa-times" aria-hidden="true"></i></button>
|
|
<h3 id="aksrt-withdraw-history-title" class="uc-modal__title"><i class="fa fas fa-file-alt mr6" aria-hidden="true"></i><?php esc_html_e('提现记录', 'aksrt'); ?></h3>
|
|
<div class="uc-withdraw-history" data-aksrt-withdraw-history-list aria-live="polite"></div>
|
|
<nav class="uc-withdraw-history__pagination" data-aksrt-withdraw-history-pagination aria-label="<?php esc_attr_e('提现记录分页', 'aksrt'); ?>"></nav>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (empty($skip_wallet_ledger)) : ?>
|
|
<?php require __DIR__ . '/credits-wallet-ledger.php'; ?>
|
|
<?php endif; ?>
|