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 根类回潮)
This commit is contained in:
@@ -154,10 +154,10 @@ class SchemaPayment {
|
||||
['key' => 'pay_balance_product_custom_s', 'label' => '自定义充值金额', 'type' => 'switch', 'default' => true, 'help' => '允许用户手动输入充值金额'],
|
||||
['key' => 'pay_balance_product_custom_limit', 'label' => '自定义充值金额上限', 'type' => 'number', 'default' => 9999, 'min' => 1, 'max' => 99999, 'dependsOn' => ['key' => 'pay_balance_product_custom_s', 'value' => true]],
|
||||
]],
|
||||
['key' => 'pass-charge', 'title' => '卡密充值', 'fields' => [
|
||||
['key' => 'pay_balance_pass_charge_s', 'label' => '启用卡密充值余额功能', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'pay_balance_pass_charge_desc', 'label' => '卡密充值说明', 'type' => 'textarea', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_balance_pass_charge_s', 'value' => true], 'help' => '用户使用卡密充值时显示的内容,建议为提醒事项、充值协议等,支持HTML代码'],
|
||||
['key' => 'pay_balance_pass_charge_only_password', 'label' => '单密码模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_balance_pass_charge_s', 'value' => true], 'help' => '仅需输入密码,不需要卡号'],
|
||||
['key' => 'pass-charge', 'title' => '卡密兑换', 'fields' => [
|
||||
['key' => 'pay_balance_pass_charge_s', 'label' => '启用卡密兑换功能', 'type' => 'switch', 'default' => false, 'help' => '开启后用户中心余额卡片展示通用「卡密兑换」入口,支持余额 / 会员 / 积分卡密,类型按卡密自动识别'],
|
||||
['key' => 'pay_balance_pass_charge_desc', 'label' => '卡密兑换说明', 'type' => 'textarea', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_balance_pass_charge_s', 'value' => true], 'help' => '用户使用卡密兑换时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码'],
|
||||
['key' => 'pay_balance_pass_charge_only_password', 'label' => '单密码模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_balance_pass_charge_s', 'value' => true], 'help' => '余额卡密仅需输入密码,不需要卡号'],
|
||||
]],
|
||||
['key' => 'points-exchange', 'title' => '余额兑换积分', 'fields' => [
|
||||
['key' => 'pay_balance_points_exchange_s', 'label' => '启用余额兑换积分', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]],
|
||||
|
||||
@@ -487,9 +487,7 @@ class SchemaUsers {
|
||||
['key' => 'points_deduction_max', 'label' => '最大抵扣比例(%)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 100, 'help' => '预留:最多可抵订单金额百分比。当前未参与下单。'],
|
||||
]],
|
||||
['key' => 'card_pass', 'title' => '积分卡密兑换', 'fields' => [
|
||||
['key' => 'asset_points_card_enabled', 'label' => '启用积分卡密兑换', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'asset_points_card_description', 'label' => '兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'asset_points_card_enabled', 'value' => true]],
|
||||
['key' => 'asset_points_card_password_only', 'label' => '仅密码兑换', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'asset_points_card_enabled', 'value' => true]],
|
||||
['key' => 'asset_points_card_enabled', 'label' => '启用积分卡密兑换', 'type' => 'switch', 'default' => false, 'help' => '开启后积分卡密可在用户中心「卡密兑换」弹窗兑换入账'],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'points_show_balance', 'label' => '导航栏显示余额', 'type' => 'switch', 'default' => true],
|
||||
|
||||
+13
-38
@@ -42,11 +42,6 @@ class ChargeCard {
|
||||
}
|
||||
|
||||
public function register_public_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/pay/card-pass/options', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_options'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
]);
|
||||
register_rest_route('aksrt/v1', '/pay/card-pass/redeem', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_redeem'],
|
||||
@@ -89,13 +84,6 @@ class ChargeCard {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rest_options(\WP_REST_Request $request) {
|
||||
return new \WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $this->get_public_options(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function rest_redeem(\WP_REST_Request $request) {
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
@@ -124,20 +112,14 @@ class ChargeCard {
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台卡密兑换开关视图(按类型拆分)。
|
||||
*
|
||||
* balance.enabled 语义为「启用卡密兑换功能」总开关(后台 余额充值 → 卡密兑换),
|
||||
* 与收款接口 card_pass 主开关共同决定前台兑换入口是否展示。
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function get_public_options(): array {
|
||||
$labels = [
|
||||
'title' => __('卡密兑换', 'aksrt'),
|
||||
'password' => __('卡密', 'aksrt'),
|
||||
'card' => __('卡号', 'aksrt'),
|
||||
'submit_balance' => __('兑换余额', 'aksrt'),
|
||||
'submit_vip' => __('兑换会员', 'aksrt'),
|
||||
'submit_credits' => __('兑换积分', 'aksrt'),
|
||||
'placeholder_pw' => __('请输入卡密', 'aksrt'),
|
||||
'placeholder_card' => __('请输入卡号', 'aksrt'),
|
||||
];
|
||||
|
||||
// Master kill-switch on 收款接口 → 卡密充值.
|
||||
$channel_on = !class_exists('\AKSRT\Settings\PaymentRuntime')
|
||||
|| \AKSRT\Settings\PaymentRuntime::is_gateway_enabled('card_pass');
|
||||
@@ -146,8 +128,7 @@ class ChargeCard {
|
||||
return [
|
||||
'balance' => $off,
|
||||
'vip' => $off,
|
||||
'credits' => $off,
|
||||
'labels' => $labels,
|
||||
'credits' => ['enabled' => false],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -175,17 +156,15 @@ class ChargeCard {
|
||||
}
|
||||
}
|
||||
|
||||
$credits = ['enabled' => false, 'desc' => '', 'only_password' => true];
|
||||
$credits = ['enabled' => false];
|
||||
$all = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_all')
|
||||
? \AKSRT\Settings\Settings::get_all()
|
||||
: (array) get_option('aksrt_settings', []);
|
||||
if (!empty($all['asset_points_card_enabled'])) {
|
||||
// Require credits system on when available.
|
||||
$credits_on = !class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'credits_enabled') || \AKSRT\Settings\Settings::credits_enabled();
|
||||
// 积分卡仅作为后端类型闸门(assert_type_enabled);前台兑换统一走
|
||||
// 通用「卡密兑换」弹窗,无独立入口与说明/单密码配置。
|
||||
$credits = [
|
||||
'enabled' => $credits_on,
|
||||
'desc' => sanitize_textarea_field((string) ($all['asset_points_card_description'] ?? '')),
|
||||
'only_password' => !isset($all['asset_points_card_password_only']) || !empty($all['asset_points_card_password_only']),
|
||||
'enabled' => !class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'credits_enabled') || \AKSRT\Settings\Settings::credits_enabled(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -193,7 +172,6 @@ class ChargeCard {
|
||||
'balance' => $balance,
|
||||
'vip' => $vip,
|
||||
'credits' => $credits,
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -808,6 +786,8 @@ class ChargeCard {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 卡密核销不发生真实支付(现金在售卡时已确认),订单金额恒为 0;
|
||||
// 卡密面额/时长记录在 note.meta,订单名称由 UserOrders 按 note 生成。
|
||||
$product_type = \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE;
|
||||
$product_id = $card_id;
|
||||
$amount = '0.00';
|
||||
@@ -815,14 +795,9 @@ class ChargeCard {
|
||||
|
||||
if ($type === self::TYPE_VIP) {
|
||||
$product_type = \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP;
|
||||
$amount = '0.00';
|
||||
} elseif ($type === self::TYPE_BALANCE) {
|
||||
$parsed_amount = \AKSRT\Ledger\AssetAmount::balance(is_string($meta['amount'] ?? null) ? $meta['amount'] : '');
|
||||
$amount = $parsed_amount->decimal();
|
||||
$currency = 'CNY';
|
||||
} elseif ($type === self::TYPE_CREDITS) {
|
||||
$product_type = \AKSRT\Core\DomainModel::ORDER_PRODUCT_POINTS;
|
||||
$amount = (int) ($meta['points'] ?? 0);
|
||||
$amount = '0';
|
||||
$currency = 'points';
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,12 @@ class UserOrders {
|
||||
$image = $this->resolve_image($meta, $product_id);
|
||||
$group = $this->status_group($status);
|
||||
$payment_method = sanitize_key((string) ($order['payment_method'] ?? ''));
|
||||
// 卡密核销不发生任何真实支付(现金在售卡时已确认),实付恒为 0,
|
||||
// 视图层统一归零以覆盖历史行(历史数据不做迁移)。
|
||||
$amount = (string) ($order['amount'] ?? '0.00');
|
||||
if ($payment_method === 'card_pass') {
|
||||
$amount = $is_points ? '0' : '0.00';
|
||||
}
|
||||
$can_pay = $group === OrderCenterQuery::STATUS_PENDING;
|
||||
$can_cancel = $group === OrderCenterQuery::STATUS_PENDING;
|
||||
$order_number = (string) ($order['order_number'] ?? '');
|
||||
@@ -194,7 +200,7 @@ class UserOrders {
|
||||
$view = [
|
||||
'id' => (int) ($order['id'] ?? 0),
|
||||
'order_number' => $order_number,
|
||||
'amount' => (string) ($order['amount'] ?? '0.00'),
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'currency_symbol' => $is_points
|
||||
? (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'))
|
||||
@@ -288,6 +294,13 @@ class UserOrders {
|
||||
|
||||
/** @param array<string, mixed> $meta */
|
||||
private function resolve_title(array $meta, string $type, int $product_id, string $fallback): string {
|
||||
// 卡密核销订单:按 note.meta 生成可读名称(余额/积分卡记面额,会员卡记档位与时长)。
|
||||
if (!empty($meta['card_pass'])) {
|
||||
$title = $this->resolve_card_pass_title($meta);
|
||||
if ($title !== '') {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
foreach (['product_title', 'title', 'package_title'] as $key) {
|
||||
if (!empty($meta[$key]) && is_string($meta[$key])) {
|
||||
return (string) $meta[$key];
|
||||
@@ -315,6 +328,43 @@ class UserOrders {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡密核销订单名称:
|
||||
* - 余额卡:卡密兑换-余额充值{面额}
|
||||
* - 积分卡:卡密兑换-积分充值{点数}
|
||||
* - 会员卡:购入[{会员档位名}]{N}天(永久卡为「永久」)
|
||||
*
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_card_pass_title(array $meta): string {
|
||||
$card_meta = is_array($meta['meta'] ?? null) ? $meta['meta'] : [];
|
||||
$card_type = sanitize_key((string) ($meta['card_type'] ?? ''));
|
||||
if ($card_type === ChargeCard::TYPE_BALANCE) {
|
||||
$amount = (string) ($card_meta['amount'] ?? '');
|
||||
if (str_contains($amount, '.')) {
|
||||
$amount = rtrim(rtrim($amount, '0'), '.');
|
||||
}
|
||||
return __('卡密兑换-余额充值', 'aksrt') . $amount;
|
||||
}
|
||||
if ($card_type === ChargeCard::TYPE_CREDITS) {
|
||||
return __('卡密兑换-积分充值', 'aksrt') . (string) max(0, (int) ($card_meta['points'] ?? 0));
|
||||
}
|
||||
if ($card_type === ChargeCard::TYPE_VIP) {
|
||||
$level = max(1, min(2, (int) ($card_meta['level'] ?? 1)));
|
||||
$days = max(0, (int) ($card_meta['days'] ?? 0));
|
||||
$levels = class_exists('\AKSRT\Settings\Settings')
|
||||
? (\AKSRT\Settings\Settings::get_payment_vip_settings()['levels'] ?? [])
|
||||
: [];
|
||||
$level_name = is_array($levels) ? (string) ($levels[$level]['name'] ?? '') : '';
|
||||
if ($level_name === '') {
|
||||
$level_name = 'VIP' . $level;
|
||||
}
|
||||
$suffix = $days > 0 ? sprintf(__('%d天', 'aksrt'), $days) : __('永久', 'aksrt');
|
||||
return sprintf(__('购入[%1$s]%2$s', 'aksrt'), $level_name, $suffix);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $meta */
|
||||
private function resolve_image(array $meta, int $product_id): string {
|
||||
if (!empty($meta['thumb']) && is_string($meta['thumb'])) {
|
||||
|
||||
Reference in New Issue
Block a user