fix(pay): 修复后台收款接口显示顺序不可用并接入收银台排序
后台「收款接口 → 收款接口显示顺序」有三处缺陷叠加: 1) sortable 渲染组件读取 item.label,而 schema 写的是 title,导致 6 行 只剩上下箭头; 2) SchemaProvider 的 sortable 分支遍历 items 定义重建数组,而该字段的 键顺序本身就是配置值,用户排序被静默改回 schema 静态顺序; 3) 真正构建收银台支付方式列表的 aksrt_list_cash_payment_methods() 使用 硬编码顺序,Gateway::get_available()/apply_sdk_order() 那套排序逻辑 无任何调用方,配置对前台毫无影响。 改为:schema 项统一使用 label(JS 与 PHP 两处对齐);sortable 持久化按 提交键顺序重建,items 定义仅作白名单;新增唯一排序实现 aksrt_sort_payment_methods()(语义对齐参考主题 zibpay_get_payment_methods + pay_sdk_order 键顺序),并接入内容收银台、余额充值、购买积分与会员 购买四个入口。卡密输入盒改为按渠道存在与否判断,不再假定卡密固定在末位。 清理死代码:无调用方的 Gateway::get_available/apply_sdk_order/ get_status_summary、VipServicePackages 中不可达的支付方式列表回退分支、 零引用的 settings-defaults 模块及 schema/index.js 未使用的导出、支付 sortable 的 fields 死元数据、无拖拽实现却声明的 cursor:move。 未配置 pay_sdk_order 时顺序保持不变;未写数据迁移。 验证:php -l 8 个改动文件全部通过;新增 pay-sdk-order-contract 契约测试 (6 项);node --test tests/widget-system/*.test.cjs 301 项全部通过; 后台 SPA 已重新构建。
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
!/tests/widget-system/card-pass-universal-redeem.test.cjs
|
||||
!/tests/widget-system/card-pass-modal-redeem.test.cjs
|
||||
!/tests/widget-system/balance-charge-modal.test.cjs
|
||||
!/tests/widget-system/pay-sdk-order-contract.test.cjs
|
||||
!/tests/admin/
|
||||
/tests/admin/*
|
||||
!/tests/admin/charge-cards-page-contract.test.cjs
|
||||
|
||||
@@ -498,8 +498,8 @@
|
||||
<n-button text size="tiny" :disabled="index === sortableItems.length - 1" @click="moveSortable(index, 1)">↓</n-button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="aksrt-field__sortable-body">
|
||||
<AksField v-for="field in item.fields || []" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
<div v-if="item.fields && item.fields.length" class="aksrt-field__sortable-body">
|
||||
<AksField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1365,7 +1365,6 @@ function confirmRepeaterModal() {
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.aksrt-field__sortable-body {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ALL_SCHEMAS, CANONICAL_SCHEMA_DOMAINS, normalizeSchemaDomain } from '@/schema/index.js'
|
||||
|
||||
function cloneDefaults(defaults = {}) {
|
||||
return JSON.parse(JSON.stringify(defaults))
|
||||
}
|
||||
|
||||
export function getSectionDefaults(domain, section) {
|
||||
const domainKey = normalizeSchemaDomain(domain)
|
||||
const sectionSchema = ALL_SCHEMAS[domainKey]?.[section]
|
||||
return cloneDefaults(sectionSchema?.defaults || {})
|
||||
}
|
||||
|
||||
export function getDomainDefaults(domain) {
|
||||
const domainKey = normalizeSchemaDomain(domain)
|
||||
const sections = Object.values(ALL_SCHEMAS[domainKey] || {})
|
||||
|
||||
return sections.reduce((defaults, schema) => {
|
||||
return {
|
||||
...defaults,
|
||||
...cloneDefaults(schema?.defaults || {}),
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
export const SETTINGS_DEFAULTS_BY_DOMAIN = CANONICAL_SCHEMA_DOMAINS.reduce((defaults, domain) => {
|
||||
defaults[domain] = getDomainDefaults(domain)
|
||||
return defaults
|
||||
}, {})
|
||||
@@ -43,17 +43,6 @@ export function normalizeSchemaDomain(domain = '') {
|
||||
return value.replace(/-/g, '_')
|
||||
}
|
||||
|
||||
export const CANONICAL_SCHEMA_DOMAINS = [
|
||||
'global',
|
||||
'page_display',
|
||||
'post_list',
|
||||
'permissions',
|
||||
'users',
|
||||
'payment',
|
||||
'extensions',
|
||||
'tools',
|
||||
]
|
||||
|
||||
/** All schemas organized by domain */
|
||||
export const ALL_SCHEMAS = Object.freeze({
|
||||
global: globalSchemas,
|
||||
@@ -77,31 +66,3 @@ export function getSchema(domain, section) {
|
||||
if (!domainSchemas) return null
|
||||
return domainSchemas[section] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all schemas for a domain.
|
||||
* @param {string} domain
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getDomainSchemas(domain) {
|
||||
return ALL_SCHEMAS[normalizeSchemaDomain(domain)] || {}
|
||||
}
|
||||
|
||||
export function getSchemaSectionKeys(domain) {
|
||||
return Object.keys(getDomainSchemas(domain))
|
||||
}
|
||||
|
||||
export function getSchemaDefaults(domain, section = '') {
|
||||
const domainKey = normalizeSchemaDomain(domain)
|
||||
|
||||
if (section) {
|
||||
return { ...(getSchema(domainKey, section)?.defaults || {}) }
|
||||
}
|
||||
|
||||
return Object.values(getDomainSchemas(domainKey)).reduce((defaults, schema) => {
|
||||
return {
|
||||
...defaults,
|
||||
...(schema?.defaults || {}),
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -187,12 +187,12 @@ export const gatewaySchema = {
|
||||
type: 'sortable',
|
||||
default: {},
|
||||
items: [
|
||||
{ key: 'wechat', title: '微信', fields: [] },
|
||||
{ key: 'alipay', title: '支付宝', fields: [] },
|
||||
{ key: 'paypal', title: 'PayPal(贝宝)', fields: [] },
|
||||
{ key: 'stripe', title: 'Stripe', fields: [] },
|
||||
{ key: 'balance', title: '余额', fields: [] },
|
||||
{ key: 'card_pass', title: '卡密', fields: [] },
|
||||
{ key: 'wechat', label: '微信' },
|
||||
{ key: 'alipay', label: '支付宝' },
|
||||
{ key: 'paypal', label: 'PayPal(贝宝)' },
|
||||
{ key: 'stripe', label: 'Stripe' },
|
||||
{ key: 'balance', label: '余额' },
|
||||
{ key: 'card_pass', label: '卡密' },
|
||||
],
|
||||
help: '收银台支付方式显示顺序;仅显示已开启的接口',
|
||||
},
|
||||
|
||||
@@ -136,8 +136,77 @@ if (!function_exists('aksrt_list_cash_payment_methods')) {
|
||||
|
||||
// Do not inject a fake balance/alipay option when nothing is configured —
|
||||
// callers should surface “暂无可用支付方式” instead of a dead method.
|
||||
return aksrt_sort_payment_methods($methods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 「收款接口显示顺序」槽位 → 网关 id 映射。
|
||||
*
|
||||
* 后台 sortable 项的 key 与之一一对应;微信/支付宝槽位同时收纳易支付子通道。
|
||||
*
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
if (!function_exists('aksrt_payment_order_slots')) {
|
||||
function aksrt_payment_order_slots(): array {
|
||||
return [
|
||||
'wechat' => ['wechatpay', 'epay_wxpay'],
|
||||
'alipay' => ['alipay', 'epay_alipay'],
|
||||
'paypal' => ['paypal'],
|
||||
'stripe' => ['stripe'],
|
||||
'balance' => ['balance'],
|
||||
'card_pass' => ['card_pass'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按后台「收款接口显示顺序」(pay_sdk_order)重排支付方式列表。
|
||||
*
|
||||
* 语义对齐参考主题 zibpay_get_payment_methods():按配置槽位顺序把命中的方式
|
||||
* 依次提到前面,未纳入槽位的方式保持原有相对顺序并留在末尾;未配置顺序时
|
||||
* 原样返回(即保持注册顺序)。这是排序的唯一实现,所有收银台构建支付方式
|
||||
* 列表的位置都必须经由此函数。
|
||||
*
|
||||
* @param array<int, array{id:string,name:string}> $methods
|
||||
* @return array<int, array{id:string,name:string}>
|
||||
*/
|
||||
if (!function_exists('aksrt_sort_payment_methods')) {
|
||||
function aksrt_sort_payment_methods(array $methods): array {
|
||||
if (!$methods) {
|
||||
return $methods;
|
||||
}
|
||||
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$configured = is_array($settings) ? ($settings['pay_sdk_order'] ?? []) : [];
|
||||
if (!is_array($configured) || $configured === []) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
$slots = aksrt_payment_order_slots();
|
||||
$remaining = $methods;
|
||||
$sorted = [];
|
||||
foreach (array_keys($configured) as $slot) {
|
||||
$slot = sanitize_key((string) $slot);
|
||||
if ($slot === '') {
|
||||
continue;
|
||||
}
|
||||
foreach ($slots[$slot] ?? [] as $gateway_id) {
|
||||
foreach ($remaining as $index => $method) {
|
||||
if ((string) ($method['id'] ?? '') !== $gateway_id) {
|
||||
continue;
|
||||
}
|
||||
$sorted[] = $method;
|
||||
unset($remaining[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($remaining as $method) {
|
||||
$sorted[] = $method;
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -73,6 +73,10 @@ $currency = (string) ($currency_name ?? __('积分', 'aksrt'));
|
||||
if (!empty($points_card_pass['enabled'])) {
|
||||
$points_methods[] = ['id' => 'card_pass', 'name' => __('卡密', 'aksrt')];
|
||||
}
|
||||
// 「收款接口显示顺序」:购买积分弹窗只含余额与卡密两个槽位。
|
||||
if (function_exists('aksrt_sort_payment_methods')) {
|
||||
$points_methods = aksrt_sort_payment_methods($points_methods);
|
||||
}
|
||||
$points_custom_limit_html = sprintf(__('最低购买%d积分,最高购买%d积分', 'aksrt'), $pb_min_points, $pb_daily_max_points);
|
||||
$points_gateway = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_gateway_settings() : [];
|
||||
$points_mark = (string) ($points_gateway['currency']['mark'] ?? '¥');
|
||||
|
||||
@@ -116,7 +116,9 @@ $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';
|
||||
$charge_card_in_methods = count($methods) > 0 && $methods[count($methods) - 1]['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>'
|
||||
|
||||
@@ -414,12 +414,12 @@ class SchemaPayment {
|
||||
]],
|
||||
['key' => 'sdk-order', 'title' => '收款接口显示顺序', 'fields' => [
|
||||
['key' => 'pay_sdk_order', 'label' => '显示顺序', 'type' => 'sortable', 'default' => [], 'items' => [
|
||||
['key' => 'wechat', 'title' => '微信', 'fields' => []],
|
||||
['key' => 'alipay', 'title' => '支付宝', 'fields' => []],
|
||||
['key' => 'paypal', 'title' => 'PayPal(贝宝)', 'fields' => []],
|
||||
['key' => 'stripe', 'title' => 'Stripe', 'fields' => []],
|
||||
['key' => 'balance', 'title' => '余额', 'fields' => []],
|
||||
['key' => 'card_pass', 'title' => '卡密', 'fields' => []],
|
||||
['key' => 'wechat', 'label' => '微信'],
|
||||
['key' => 'alipay', 'label' => '支付宝'],
|
||||
['key' => 'paypal', 'label' => 'PayPal(贝宝)'],
|
||||
['key' => 'stripe', 'label' => 'Stripe'],
|
||||
['key' => 'balance', 'label' => '余额'],
|
||||
['key' => 'card_pass', 'label' => '卡密'],
|
||||
], 'help' => '收银台支付方式显示顺序;仅显示已开启的接口'],
|
||||
]],
|
||||
['key' => 'alipay', 'title' => '支付宝官方', 'collapsible' => true, 'expandWhen' => 'pay_alipay_sdk_options', 'dependsOn' => ['key' => 'pay_alipay_sdk_options', 'value' => 'official_alipay'], 'fields' => [
|
||||
|
||||
@@ -942,12 +942,25 @@ class SchemaProvider {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
$items = [];
|
||||
$definitions = [];
|
||||
foreach ((array) ($field['items'] ?? []) as $item_schema) {
|
||||
$item_key = sanitize_key((string) ($item_schema['key'] ?? ''));
|
||||
if ($item_key !== '' && array_key_exists($item_key, $value) && is_array($value[$item_key])) {
|
||||
$items[$item_key] = $this->sanitize_object_fields($value[$item_key], (array) ($item_schema['fields'] ?? []));
|
||||
if (!is_array($item_schema)) {
|
||||
continue;
|
||||
}
|
||||
$item_key = sanitize_key((string) ($item_schema['key'] ?? ''));
|
||||
if ($item_key !== '') {
|
||||
$definitions[$item_key] = $item_schema;
|
||||
}
|
||||
}
|
||||
// 键顺序即配置值本身:必须按提交顺序重建。若改为遍历 items
|
||||
// 定义,用户排序会被静默改写回 schema 静态顺序(等于排序失效)。
|
||||
$items = [];
|
||||
foreach ($value as $item_key => $item_value) {
|
||||
$item_key = sanitize_key((string) $item_key);
|
||||
if ($item_key === '' || !isset($definitions[$item_key]) || !is_array($item_value)) {
|
||||
continue;
|
||||
}
|
||||
$items[$item_key] = $this->sanitize_object_fields($item_value, (array) ($definitions[$item_key]['fields'] ?? []));
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
@@ -517,28 +517,9 @@ trait VipServicePackages {
|
||||
* @return array<int, array{id:string,name:string}>
|
||||
*/
|
||||
private function available_payment_methods(): array {
|
||||
$methods = [];
|
||||
if (function_exists('aksrt_list_cash_payment_methods')) {
|
||||
$methods = aksrt_list_cash_payment_methods(true);
|
||||
} else {
|
||||
if (!class_exists('\AKSRT\Payment\Gateway')) {
|
||||
return [];
|
||||
}
|
||||
$gw = \AKSRT\Payment\Gateway::get_instance();
|
||||
foreach (['balance', 'alipay', 'wechatpay', 'paypal', 'stripe', 'epay_alipay', 'epay_wxpay'] as $id) {
|
||||
$g = $gw->get_gateway($id);
|
||||
if (!$g) {
|
||||
continue;
|
||||
}
|
||||
if (method_exists($g, 'is_configured') && !$g->is_configured()) {
|
||||
continue;
|
||||
}
|
||||
$methods[] = [
|
||||
'id' => $id,
|
||||
'name' => method_exists($g, 'get_name') ? (string) $g->get_name() : $id,
|
||||
];
|
||||
}
|
||||
}
|
||||
$methods = function_exists('aksrt_list_cash_payment_methods')
|
||||
? aksrt_list_cash_payment_methods(true)
|
||||
: [];
|
||||
|
||||
// 卡密兑换会员渠道(后台 会员购买 → 启用卡密兑换会员功能):
|
||||
// 提交走 /pay/card-pass/redeem type=vip,不创建支付订单。
|
||||
@@ -546,7 +527,10 @@ trait VipServicePackages {
|
||||
if (!empty($card_pass['enabled'])) {
|
||||
$methods[] = ['id' => 'card_pass', 'name' => __('卡密', 'aksrt')];
|
||||
}
|
||||
return $methods;
|
||||
// 卡密渠道追加后需按「收款接口显示顺序」重新定位(含 card_pass 槽位)。
|
||||
return function_exists('aksrt_sort_payment_methods')
|
||||
? aksrt_sort_payment_methods($methods)
|
||||
: $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -161,6 +161,10 @@ trait BalanceServiceCharge {
|
||||
if ($card_pass_on) {
|
||||
$methods[] = ['id' => 'card_pass', 'name' => __('卡密', 'aksrt')];
|
||||
}
|
||||
// 卡密渠道追加后需按「收款接口显示顺序」重新定位(含 card_pass 槽位)。
|
||||
if (function_exists('aksrt_sort_payment_methods')) {
|
||||
$methods = aksrt_sort_payment_methods($methods);
|
||||
}
|
||||
return [
|
||||
'enabled' => !empty($cfg['enabled']),
|
||||
'desc' => (string) ($cfg['desc'] ?? ''),
|
||||
|
||||
@@ -44,76 +44,6 @@ class Gateway {
|
||||
return $this->gateways;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用网关列表(已配置的),按「收款接口显示顺序」配置排序。
|
||||
*/
|
||||
public function get_available(): array {
|
||||
$available = [];
|
||||
foreach ($this->gateways as $id => $gateway) {
|
||||
if ($gateway->is_configured()) {
|
||||
$available[$id] = $gateway;
|
||||
}
|
||||
}
|
||||
return $this->apply_sdk_order($available);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 pay_sdk_order(收款接口显示顺序)重排可用网关。
|
||||
* 易支付子通道跟随对应渠道槽位(epay_wxpay 跟随 wechat、epay_alipay 跟随 alipay);
|
||||
* 未配置顺序时保持注册顺序,未纳入槽位的扩展网关保持在末尾。
|
||||
*
|
||||
* @param array<string,GatewayInterface> $available
|
||||
* @return array<string,GatewayInterface>
|
||||
*/
|
||||
private function apply_sdk_order(array $available): array {
|
||||
$settings = \AKSRT\Settings\Settings::get_all();
|
||||
$configured = (array) ($settings['pay_sdk_order'] ?? []);
|
||||
$order = array_values(array_filter(array_map('sanitize_key', array_keys($configured))));
|
||||
if (!$order) {
|
||||
return $available;
|
||||
}
|
||||
|
||||
$slot_map = [
|
||||
'wechat' => ['wechatpay', 'epay_wxpay'],
|
||||
'alipay' => ['alipay', 'epay_alipay'],
|
||||
'paypal' => ['paypal'],
|
||||
'stripe' => ['stripe'],
|
||||
'balance' => ['balance'],
|
||||
'card_pass' => ['card_pass'],
|
||||
];
|
||||
|
||||
$sorted = [];
|
||||
foreach ($order as $slot) {
|
||||
foreach (($slot_map[$slot] ?? []) as $id) {
|
||||
if (isset($available[$id]) && !isset($sorted[$id])) {
|
||||
$sorted[$id] = $available[$id];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($available as $id => $gateway) {
|
||||
if (!isset($sorted[$id])) {
|
||||
$sorted[$id] = $gateway;
|
||||
}
|
||||
}
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网关状态摘要(用于后台展示)
|
||||
*/
|
||||
public function get_status_summary(): array {
|
||||
$summary = [];
|
||||
foreach ($this->gateways as $id => $gateway) {
|
||||
$summary[] = [
|
||||
'id' => $id,
|
||||
'name' => $gateway->get_name(),
|
||||
'configured' => $gateway->is_configured(),
|
||||
'available' => $gateway->is_configured(),
|
||||
];
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定支付回调路由。必须在 functions.php 中初始化。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 「收款接口显示顺序」契约(后台 sortable 控件 → 收银台支付方式顺序)。
|
||||
*
|
||||
* 背景:后台 收款接口 → 收款接口显示顺序 曾整列空白、保存后顺序被静默改写,
|
||||
* 且没有任何前台代码消费该配置。本契约锁定四件事:
|
||||
* 1) sortable 项使用 AksField 实际读取的 `label` 键(写 `title` 会渲染成空白);
|
||||
* 2) 持久化必须保留用户提交的键顺序(键顺序即配置值本身);
|
||||
* 3) 排序只保留一份实现,并且真的作用到收银台;
|
||||
* 4) 相关的重复实现 / 死模块不回潮。
|
||||
*/
|
||||
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 schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
|
||||
const schemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'gateway.js');
|
||||
const aksField = read('assets', 'admin', 'src', 'components', 'form', 'AksField.vue');
|
||||
const provider = read('src', 'Admin', 'SchemaProvider.php');
|
||||
const pay = read('inc', 'helpers', 'aksrt-pay.php');
|
||||
const gateway = read('src', 'Payment', 'Gateway.php');
|
||||
const wallet = read('inc', 'helpers', 'user-center-renderers', 'credits-wallet.php');
|
||||
const pointsRenderer = read('inc', 'helpers', 'user-center-renderers', 'credits-balance.php');
|
||||
const vipPackages = read('src', 'Content', 'VipServicePackages.php');
|
||||
const balanceCharge = read('src', 'Credits', 'BalanceServiceCharge.php');
|
||||
|
||||
test('sortable 项使用渲染组件读取的 label 键', () => {
|
||||
// 渲染侧读取 item.label —— items 里写 title 会让每一行只剩上下箭头。
|
||||
assert.match(aksField, /\{\{\s*item\.label\s*\}\}/);
|
||||
|
||||
assert.match(schemaJs, /items:\s*\[\s*\{ key: 'wechat', label: '微信' \}/);
|
||||
assert.match(schemaJs, /\{ key: 'card_pass', label: '卡密' \}/);
|
||||
assert.doesNotMatch(schemaJs, /title: '微信'/);
|
||||
|
||||
assert.match(schemaPhp, /\['key' => 'wechat', 'label' => '微信'\]/);
|
||||
assert.match(schemaPhp, /\['key' => 'card_pass', 'label' => '卡密'\]/);
|
||||
assert.doesNotMatch(schemaPhp, /'key' => 'wechat', 'title' => '微信'/);
|
||||
});
|
||||
|
||||
test('sortable 持久化保留提交键顺序', () => {
|
||||
const start = provider.indexOf("if ($type === 'sortable') {");
|
||||
const end = provider.indexOf("if ($type === 'link') {", start);
|
||||
assert.ok(start >= 0 && end > start, 'SchemaProvider 应存在 sortable 分支');
|
||||
|
||||
const block = provider.slice(start, end);
|
||||
const resultLoopStart = block.indexOf('foreach ($value as $item_key');
|
||||
assert.ok(resultLoopStart > 0, '结果必须按提交顺序(遍历 $value)构建');
|
||||
|
||||
const definitionsLoop = block.slice(0, resultLoopStart);
|
||||
const resultLoop = block.slice(resultLoopStart);
|
||||
|
||||
// items 定义只用于白名单校验,不得据此产出结果键顺序。
|
||||
assert.doesNotMatch(definitionsLoop, /\$items\[/);
|
||||
assert.match(definitionsLoop, /\$definitions\[\$item_key\] = \$item_schema;/, 'items 定义应收敛为白名单');
|
||||
// 键顺序即排序值:按提交顺序写入结果,且丢弃未定义的槽位键。
|
||||
assert.match(resultLoop, /!isset\(\$definitions\[\$item_key\]\)/);
|
||||
assert.match(resultLoop, /\$items\[\$item_key\] = \$this->sanitize_object_fields/);
|
||||
});
|
||||
|
||||
test('排序只有一份实现,无调用方的重复实现已移除', () => {
|
||||
assert.match(pay, /if \(!function_exists\('aksrt_sort_payment_methods'\)\)/);
|
||||
assert.match(pay, /function aksrt_sort_payment_methods\(array \$methods\): array/);
|
||||
assert.match(pay, /\$configured = is_array\(\$settings\) \? \(\$settings\['pay_sdk_order'\] \?\? \[\]\) : \[\];/);
|
||||
// 槽位映射与后台 sortable 项一一对应,易支付子通道随所属渠道槽位。
|
||||
assert.match(pay, /'wechat'\s+=> \['wechatpay', 'epay_wxpay'\]/);
|
||||
assert.match(pay, /'alipay'\s+=> \['alipay', 'epay_alipay'\]/);
|
||||
assert.match(pay, /'card_pass' => \['card_pass'\]/);
|
||||
|
||||
// 旧的无调用方实现(第二套排序逻辑)已删除。
|
||||
assert.doesNotMatch(gateway, /function get_available\(/);
|
||||
assert.doesNotMatch(gateway, /function apply_sdk_order\(/);
|
||||
assert.doesNotMatch(gateway, /function get_status_summary\(/);
|
||||
});
|
||||
|
||||
test('基础渠道列表与卡密渠道都经过同一排序实现', () => {
|
||||
// 内容收银台 / 会员购买 / 余额充值共用基础列表。
|
||||
assert.match(pay, /return aksrt_sort_payment_methods\(\$methods\);/);
|
||||
assert.match(balanceCharge, /aksrt_sort_payment_methods\(\$methods\)/);
|
||||
assert.match(vipPackages, /aksrt_sort_payment_methods\(\$methods\)/);
|
||||
// 购买积分弹窗自建余额 + 卡密列表,同样排序。
|
||||
assert.match(pointsRenderer, /aksrt_sort_payment_methods\(\$points_methods\)/);
|
||||
// 卡密输入盒是 method 条件块,位置无关:卡密渠道可被移到任意槽位。
|
||||
assert.match(wallet, /in_array\('card_pass', array_column\(\$methods, 'id'\), true\)/);
|
||||
});
|
||||
|
||||
test('会员弹窗不再保留重复的支付方式列表回退分支', () => {
|
||||
assert.doesNotMatch(vipPackages, /class_exists\('\\\\AKSRT\\\\Payment\\\\Gateway'\)/);
|
||||
// 卡密渠道注入契约保持(对齐参考主题 zibpay_get_payment_methods)。
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'aksrt'\)/);
|
||||
});
|
||||
|
||||
test('未被引用的后台 schema 默认值死模块不回潮', () => {
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(root, 'assets', 'admin', 'src', 'constants', 'settings-defaults.js')),
|
||||
false,
|
||||
'settings-defaults.js 无任何引用方,应保持删除'
|
||||
);
|
||||
const schemaIndex = read('assets', 'admin', 'src', 'schema', 'index.js');
|
||||
assert.doesNotMatch(schemaIndex, /getSchemaDefaults|getSchemaSectionKeys|getDomainSchemas|CANONICAL_SCHEMA_DOMAINS/);
|
||||
});
|
||||
Reference in New Issue
Block a user