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:
2026-09-18 19:01:19 +08:00
parent 530a5b998f
commit d2447f8aa6
14 changed files with 223 additions and 181 deletions
+6 -6
View File
@@ -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' => [
+17 -4
View File
@@ -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;
}
+7 -23
View File
@@ -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;
}
/**
+4
View File
@@ -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'] ?? ''),
-70
View File
@@ -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 中初始化。
*