feat(withdrawal): make payout channels a configurable sortable list
The payout channel was a two-item checkbox (alipay / wechat). Sites that pay out through a bank transfer, USDT or any other route had no way to express it, and every surface behind it — the withdrawal modal, the server-side check, the admin review list — hard-coded those two names. affiliate_payout_channels is now a repeater on both the PHP and Vue schemas, using the same draggable component as the post filters and gateway config. Each row carries a stable key, a display label, an account placeholder, an optional note and an enabled flag. Disabled channels disappear from the front end but stay resolvable, so historical withdrawal records still render their original name. PaymentRuntime::payout_channels() normalises the stored structure: it drops rows with an empty key or label, de-duplicates keys, and falls back to the built-in two channels when nothing is configured. enabled_payout_channels() feeds the front end and payout_channel() resolves a single key for record display. CommissionLedger's validation now compares against the configured, enabled keys instead of a literal allowlist. The withdrawal modal renders the configured list and swaps the account placeholder and note when the channel changes; credits.js drives that. The admin review page reads the same settings for its method labels and filter, and the review dialog gained a "渠道说明" row so the operator sees the payout instruction, hidden when empty. Default channels live in SchemaPayment as plain data — the runtime imports them rather than the reverse, because the schema layer must not depend on a runtime settings class (doing it the other way made the schema harness fatal). Verification: drove payout_channels() through default / custom / dirty / duplicate inputs and confirmed enabled filtering, note lookup and disabled-channel resolution. Added payment-withdrawal-channels-contract.test.cjs covering the schema shape, runtime helpers, front-end wiring and admin read path. 873 tests pass, PSR-4 audit clean, admin SPA builds.
This commit is contained in:
1 parent
bcba0f598a
commit
dcd306b428
10 files changed
+267
-20
No files matched your search
@@ -176,12 +176,40 @@ class PayWithdrawals {
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
/**
|
||||
* 收款渠道标识 → 名称。
|
||||
*
|
||||
* 渠道由后台「提现策略 → 收款渠道」的拖拽结构定义,可自定义名称;这里读同一份
|
||||
* 配置,停用的渠道也会返回,历史提现记录才能按原名称展示。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function method_labels(): array {
|
||||
return [
|
||||
'alipay' => __('支付宝', 'jio'),
|
||||
'wechat' => __('微信', 'jio'),
|
||||
];
|
||||
$labels = [];
|
||||
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_withdrawal_settings() : [];
|
||||
foreach ((array) ($settings['payout_channels'] ?? []) as $channel) {
|
||||
if (!is_array($channel) || (string) ($channel['key'] ?? '') === '') {
|
||||
continue;
|
||||
}
|
||||
$labels[(string) $channel['key']] = (string) ($channel['label'] ?? $channel['key']);
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/** 渠道补充说明(后台「收款渠道 → 渠道说明」),审核弹窗里展示给操作人。 */
|
||||
private function method_notes(): array {
|
||||
$notes = [];
|
||||
$settings = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_withdrawal_settings() : [];
|
||||
foreach ((array) ($settings['payout_channels'] ?? []) as $channel) {
|
||||
if (!is_array($channel) || (string) ($channel['key'] ?? '') === '') {
|
||||
continue;
|
||||
}
|
||||
$note = trim((string) ($channel['note'] ?? ''));
|
||||
if ($note !== '') {
|
||||
$notes[(string) $channel['key']] = $note;
|
||||
}
|
||||
}
|
||||
return $notes;
|
||||
}
|
||||
|
||||
private function format_money(string $decimal): string {
|
||||
|
||||
@@ -176,6 +176,7 @@ trait PayWithdrawalsList {
|
||||
'gross' => $this->format_money($gross),
|
||||
'source' => $source_label,
|
||||
'payee' => $method_label . ($account !== '' ? ' · ' . $account : ''),
|
||||
'method_note' => (string) ($this->method_notes()[$method] ?? ''),
|
||||
];
|
||||
$attrs = '';
|
||||
foreach ($data as $key => $value) {
|
||||
@@ -328,6 +329,7 @@ trait PayWithdrawalsList {
|
||||
'gross' => __('申请金额', 'jio'),
|
||||
'source' => __('来源', 'jio'),
|
||||
'payee' => __('收款信息', 'jio'),
|
||||
'method_note' => __('渠道说明', 'jio'),
|
||||
] as $field => $label) {
|
||||
echo '<div class="jio-wd-summary__item jio-wd-summary__item--' . esc_attr($field) . '"><dt>' . esc_html($label) . '</dt><dd data-summary="' . esc_attr($field) . '"></dd></div>';
|
||||
}
|
||||
|
||||
@@ -557,7 +557,13 @@ class SchemaPayment {
|
||||
['key' => 'affiliate_withdrawal_reset_time', 'label' => '重置时间', 'type' => 'input', 'default' => '00:00', 'help' => '按站点 WordPress 时区重置,格式 HH:MM。'],
|
||||
]],
|
||||
['key' => 'channels', 'title' => '收款渠道', 'fields' => [
|
||||
['key' => 'affiliate_payout_channels', 'label' => '可用收款渠道', 'type' => 'checkbox', 'options' => [['value' => 'alipay', 'label' => '支付宝收款码'], ['value' => 'wechat', 'label' => '微信收款码']], 'default' => ['alipay', 'wechat']],
|
||||
['key' => 'affiliate_payout_channels', 'label' => '收款渠道', 'type' => 'repeater', 'addText' => '添加收款渠道', 'default' => self::default_payout_channels(), 'description' => '用户提现时可选择的收款方式,可拖拽排序、自定义名称。停用的渠道不会在前台展示,但历史提现记录仍按原名称显示。', 'fields' => [
|
||||
['key' => 'key', 'label' => '渠道标识', 'type' => 'input', 'default' => '', 'help' => '英文小写,用于区分渠道(如 alipay、wechat、bank)。保存后请勿随意修改,历史提现记录依赖它。'],
|
||||
['key' => 'label', 'label' => '渠道名称', 'type' => 'input', 'default' => '', 'help' => '前台下拉里显示的名称,可自定义。'],
|
||||
['key' => 'account_hint', 'label' => '收款信息提示', 'type' => 'input', 'default' => '', 'help' => '用户填写收款账户时的占位提示,如「姓名 + 支付宝账号」。'],
|
||||
['key' => 'note', 'label' => '渠道说明', 'type' => 'textarea', 'default' => '', 'rows' => 2, 'help' => '选中该渠道时在收款信息下方展示的补充说明,可留空。'],
|
||||
['key' => 'enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
]],
|
||||
['key' => 'agreement', 'title' => '提现协议', 'fields' => [
|
||||
['key' => 'pay_rebate_withdraw_text_details', 'label' => '提现协议', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<div>可提现金额达到50元后即可发起提现</div><div>申请提现需后台人工处理,一般2-3小时,请耐心等待</div><div>如有其它疑问,请与客服联系</div>', 'rows' => 4, 'help' => '用户申请提现时展示的内容,建议为提现须知等,支持HTML代码'],
|
||||
@@ -568,6 +574,18 @@ class SchemaPayment {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 提现收款渠道的默认结构:与参考主题一致的两个内置渠道。
|
||||
*
|
||||
* @return list<array{key:string,label:string,account_hint:string,note:string,enabled:bool}>
|
||||
*/
|
||||
public static function default_payout_channels(): array {
|
||||
return [
|
||||
['key' => 'alipay', 'label' => '支付宝收款码', 'account_hint' => '姓名 + 支付宝账号', 'note' => '', 'enabled' => true],
|
||||
['key' => 'wechat', 'label' => '微信收款码', 'account_hint' => '姓名 + 微信号', 'note' => '', 'enabled' => true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员商品/续费商品:标题行显示价格与时长摘要(行默认收起由 SPA 统一处理)。
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user