feat(payment): 新增「售后策略」配置与前台售后弹窗链路
- 后台「支付&付费 → 售后策略」:售后功能开关、申请时效、超时效提示(HTML)、 预置原因、申请售后说明、所需材料、申请说明占位文案、可上传图片开关; PHP schema 与 SPA schema 1:1 对齐,group 字段支持拖拽排序 - PaymentRuntime 新增售后设置解析(缺键回退 schema 默认、HTML 走 kses、 中文原因稳定 slug)与 after_sale_expired 时效判定(支付时间起算) - 售后工单支持举证图片(order_aftersale.images 列,最多 3 张, 复用评论图片上传校验)与上传路由;订单视图模型输出 after_sale 按钮状态 - 前台申请 / 超时效提示 / 售后详细三个独立弹窗(modal-mini + 蓝色炫彩头), 订单详情弹窗不再内嵌售后表单并清理相关死代码 - 新增 after-sale-policy 契约测试与运行时 harness,527 项契约测试通过
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* After-sale policy runtime harness.
|
||||
*
|
||||
* Covers the 「支付&付费 → 售后策略」 runtime resolution: schema defaults when the
|
||||
* section was never saved, stored overrides, HTML sanitisation, preset-reason
|
||||
* slugs, and the after_sale_expired boundary matrix.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
define('DAY_IN_SECONDS', 86400);
|
||||
|
||||
$GLOBALS['aksrt_test_options'] = [];
|
||||
$GLOBALS['aksrt_test_now'] = 0;
|
||||
|
||||
function sanitize_key($value): string {
|
||||
return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? '';
|
||||
}
|
||||
|
||||
function sanitize_text_field($value): string {
|
||||
return trim(preg_replace('/[\r\n\t]+/', ' ', strip_tags((string) $value)) ?? '');
|
||||
}
|
||||
|
||||
function sanitize_textarea_field($value): string {
|
||||
return trim(strip_tags((string) $value));
|
||||
}
|
||||
|
||||
function wp_kses_post($value): string {
|
||||
$value = preg_replace('#<script\b[^>]*>.*?</script>#is', '', (string) $value);
|
||||
return strip_tags($value, '<a><b><strong><em><i><span><div><p><br><ul><ol><li>');
|
||||
}
|
||||
|
||||
function esc_url_raw($value): string {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
function wp_json_encode($value): string {
|
||||
return (string) json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
function get_option(string $name, $default = false) {
|
||||
return $GLOBALS['aksrt_test_options'][$name] ?? $default;
|
||||
}
|
||||
|
||||
function update_option(string $name, $value): bool {
|
||||
$GLOBALS['aksrt_test_options'][$name] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
function current_time(string $type, $gmt = false) {
|
||||
return $GLOBALS['aksrt_test_now'];
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
class Settings {
|
||||
public static function get_all(): array {
|
||||
$stored = get_option('aksrt_settings', []);
|
||||
return is_array($stored) ? $stored : [];
|
||||
}
|
||||
|
||||
public static function read_domain(string $domain, string $section = ''): array {
|
||||
return \AKSRT\Admin\SchemaProvider::get_instance()->read_settings($domain, $section);
|
||||
}
|
||||
|
||||
public static function flush_settings_cache(): void {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaPayment.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/SchemaProvider.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Settings/PaymentRuntime.php';
|
||||
|
||||
$GLOBALS['aksrt_test_now'] = strtotime('2026-03-01 12:00:00');
|
||||
|
||||
// 1) Section never saved: the runtime still resolves schema defaults.
|
||||
$defaults = \AKSRT\Settings\PaymentRuntime::get_after_sale_settings();
|
||||
|
||||
// 2) Stored values override defaults; HTML fields are kses'ed.
|
||||
update_option('aksrt_settings', [
|
||||
'after_sale_s' => false,
|
||||
'after_sale_time_limit' => 30,
|
||||
'after_sale_expired_text' => '<div>已超过时效</div><script>alert(1)</script>',
|
||||
'after_sale_reasons' => [['t' => '质量问题'], ['t' => '发错货']],
|
||||
'after_sale_desc' => '<div>请保持商品完好</div><script>alert(2)</script>',
|
||||
'after_sale_materials' => [['t' => '联系邮箱']],
|
||||
'after_sale_reason_placeholder' => '请描述问题',
|
||||
'after_sale_img_s' => false,
|
||||
]);
|
||||
$overridden = \AKSRT\Settings\PaymentRuntime::get_after_sale_settings();
|
||||
|
||||
// 3) Expiry boundary matrix: measured from paid_at, strictly beyond the limit.
|
||||
$matrix = [];
|
||||
$boundary_cases = [
|
||||
'within' => ['2026-02-25 00:00:00', 7, false],
|
||||
'exact_boundary' => ['2026-02-22 12:00:00', 7, false],
|
||||
'just_expired' => ['2026-02-22 11:59:59', 7, true],
|
||||
'long_expired' => ['2026-01-01 00:00:00', 7, true],
|
||||
'no_limit' => ['2020-01-01 00:00:00', 0, false],
|
||||
'missing_paid' => ['', 7, false],
|
||||
];
|
||||
$stored = get_option('aksrt_settings', []);
|
||||
foreach ($boundary_cases as $name => $case) {
|
||||
[$paid_at, $limit, $expected] = $case;
|
||||
$stored['after_sale_time_limit'] = $limit;
|
||||
update_option('aksrt_settings', $stored);
|
||||
$matrix[$name] = \AKSRT\Settings\PaymentRuntime::after_sale_expired($paid_at) === $expected;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'defaults' => $defaults,
|
||||
'overridden' => $overridden,
|
||||
'expired_ok' => $matrix,
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
Reference in New Issue
Block a user