Bump content sales on resource order paid; show order number/time in download box; poll pending gateway payments; support download_limit_over_price overbuy.
441 lines
16 KiB
PHP
441 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT Domain Model Registry
|
|
*
|
|
* Canonical keys, defaults, and state machines shared by helpers and services.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Domain_Model {
|
|
|
|
public const POST_META_KEY = 'aksrt_post_meta';
|
|
public const TERM_META_KEY = 'aksrt_term_meta';
|
|
|
|
public const PAY_META_KEY = '_aksrt_pay';
|
|
public const PAY_TYPE_KEY = '_aksrt_pay_type';
|
|
public const PAY_PRICE_KEY = '_aksrt_pay_price';
|
|
|
|
public const RESOURCE_MARKER_KEY = '_aksrt_is_resource';
|
|
public const RESOURCE_DOWNLOAD_COUNT_KEY = '_aksrt_res_download_count';
|
|
public const RESOURCE_VIEW_COUNT_KEY = '_aksrt_res_view_count';
|
|
public const RESOURCE_SALES_VOLUME_KEY = '_aksrt_sales_volume';
|
|
public const POST_VIEW_COUNT_KEY = 'aksrt_post_views';
|
|
|
|
public const ORDER_MIGRATED_OPTION = 'aksrt_order_migrated_to_table';
|
|
public const RESOURCE_PAY_META_MIGRATED = 'aksrt_resource_pay_meta_migrated';
|
|
|
|
public const ORDER_PRODUCT_RESOURCE = 'resource';
|
|
public const ORDER_PRODUCT_VIP = 'vip';
|
|
public const ORDER_PRODUCT_RECHARGE = 'recharge';
|
|
public const ORDER_PRODUCT_SHOP = 'shop_product';
|
|
|
|
public const PAY_TYPE_NO = 'no';
|
|
public const PAY_TYPE_READING = 'reading';
|
|
public const PAY_TYPE_DOWNLOAD = 'download';
|
|
public const PAY_TYPE_GALLERY = 'gallery';
|
|
public const PAY_TYPE_VIDEO = 'video';
|
|
public const PAY_TYPE_VIP = 'vip';
|
|
|
|
public const ORDER_STATUS_CREATED = 'created';
|
|
public const ORDER_STATUS_PENDING = 'pending';
|
|
public const ORDER_STATUS_PAID = 'paid';
|
|
public const ORDER_STATUS_DELIVERED = 'delivered';
|
|
public const ORDER_STATUS_COMPLETED = 'completed';
|
|
public const ORDER_STATUS_CANCELLED = 'cancelled';
|
|
public const ORDER_STATUS_EXPIRED = 'expired';
|
|
public const ORDER_STATUS_REFUNDING = 'refunding';
|
|
public const ORDER_STATUS_REFUNDED = 'refunded';
|
|
public const ORDER_STATUS_PENDING_REVIEW = 'pending_review';
|
|
|
|
public const SHIPPING_STATUS_PENDING = 0;
|
|
public const SHIPPING_STATUS_SHIPPED = 1;
|
|
public const SHIPPING_STATUS_COMPLETED = 2;
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function default_post_meta(): array {
|
|
return [
|
|
'cover_images' => '',
|
|
'custom_excerpt' => '',
|
|
'layout_override' => '',
|
|
'header_image_mode' => '',
|
|
'show_toc' => '',
|
|
'show_reading_progress' => '',
|
|
'enable_lightbox' => '',
|
|
'show_related' => '',
|
|
'related_count' => 0,
|
|
'enable_like' => '',
|
|
'enable_favorite' => '',
|
|
'enable_share' => '',
|
|
'enable_print' => '',
|
|
'enable_pdf' => '',
|
|
'copyright_text' => '',
|
|
'is_featured' => false,
|
|
// §3.8 文章扩展字段
|
|
'thumbnail_url' => '',
|
|
'featured_slide' => '',
|
|
'featured_video' => '',
|
|
// Episode list: [ ['url' => string, 'title' => string], ... ]
|
|
'featured_video_episode' => [],
|
|
'subtitle' => '',
|
|
'views' => 0,
|
|
'likes' => 0,
|
|
'maxheight' => 0,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function default_term_meta(): array {
|
|
return [
|
|
'cover_url' => '',
|
|
'description' => '',
|
|
'layout_mode' => '',
|
|
'show_sidebar' => '',
|
|
'show_header' => true,
|
|
'show_featured' => false,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function default_pay_meta(): array {
|
|
return [
|
|
'pay_type' => self::PAY_TYPE_NO,
|
|
'pay_modo' => '0', // '0' cash | 'points' credits (Zibll)
|
|
'price' => 0.0,
|
|
'points_price' => 0,
|
|
'original_price' => 0.0,
|
|
'vip1_price' => 0.0,
|
|
'vip2_price' => 0.0,
|
|
'vip1_points' => 0,
|
|
'vip2_points' => 0,
|
|
'pay_limit' => 0, // 0 all | 1 VIP1+ | 2 VIP2 only (Zibll)
|
|
'version' => '',
|
|
'size' => '',
|
|
'license' => '',
|
|
'compat' => '',
|
|
'demo_url' => '',
|
|
'video_url' => '',
|
|
'downloads' => [],
|
|
'attributes' => [],
|
|
'gallery' => [],
|
|
'require_login' => false,
|
|
'require_captcha' => false,
|
|
// §3.8 电商增强字段
|
|
'virtual_sales' => 0,
|
|
'stock' => -1,
|
|
'card_keys' => [],
|
|
'purchase_limit' => 0,
|
|
'free_preview' => '',
|
|
'show_sales' => true,
|
|
// Zibll: free daily quota overbuy price (cash or points by pay_modo).
|
|
'download_limit_over_price' => 0.0,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function pay_type_labels(): array {
|
|
return [
|
|
self::PAY_TYPE_NO => '不付费',
|
|
self::PAY_TYPE_READING => '付费阅读',
|
|
self::PAY_TYPE_DOWNLOAD => '付费下载',
|
|
self::PAY_TYPE_GALLERY => '付费图库',
|
|
self::PAY_TYPE_VIDEO => '付费视频',
|
|
self::PAY_TYPE_VIP => 'VIP 专属',
|
|
];
|
|
}
|
|
|
|
public static function normalize_pay_type(string $pay_type): string {
|
|
$pay_type = sanitize_key($pay_type);
|
|
return array_key_exists($pay_type, self::pay_type_labels()) ? $pay_type : self::PAY_TYPE_NO;
|
|
}
|
|
|
|
public static function is_resource_pay_type(string $pay_type): bool {
|
|
return self::normalize_pay_type($pay_type) === self::PAY_TYPE_DOWNLOAD;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<int, array<string, string>>
|
|
*/
|
|
public static function sanitize_downloads($value): array {
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
$allowed_types = ['direct', 'net_disk', 'external'];
|
|
$downloads = [];
|
|
|
|
foreach ($value as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$url = isset($row['url']) ? esc_url_raw((string) $row['url']) : '';
|
|
if ($url === '') {
|
|
continue;
|
|
}
|
|
|
|
$type = isset($row['type']) && in_array($row['type'], $allowed_types, true)
|
|
? (string) $row['type']
|
|
: 'direct';
|
|
|
|
$extract_code = '';
|
|
foreach ([
|
|
'extract_code',
|
|
'extractCode',
|
|
'extract',
|
|
'extract_key',
|
|
'extraction_code',
|
|
'extractionCode',
|
|
'code',
|
|
'password',
|
|
'pwd',
|
|
'pan_code',
|
|
'panCode',
|
|
'pan_password',
|
|
'panPassword',
|
|
'access_code',
|
|
'accessCode',
|
|
] as $extract_key) {
|
|
if (isset($row[$extract_key]) && !is_array($row[$extract_key])) {
|
|
$candidate = trim((string) $row[$extract_key]);
|
|
if ($candidate !== '') {
|
|
$extract_code = sanitize_text_field($candidate);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$downloads[] = [
|
|
'label' => isset($row['label']) ? sanitize_text_field((string) $row['label']) : '',
|
|
'url' => $url,
|
|
'type' => $type,
|
|
'extract_code' => $extract_code,
|
|
];
|
|
}
|
|
|
|
return array_values($downloads);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<int, array<string, string>>
|
|
*/
|
|
public static function sanitize_attributes($value): array {
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
$attributes = [];
|
|
|
|
foreach ($value as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$key = sanitize_text_field((string) ($row['key'] ?? ''));
|
|
$item_value = sanitize_text_field((string) ($row['value'] ?? ''));
|
|
|
|
if ($key === '' && $item_value === '') {
|
|
continue;
|
|
}
|
|
|
|
$attributes[] = [
|
|
'key' => $key,
|
|
'value' => $item_value,
|
|
];
|
|
}
|
|
|
|
return array_values($attributes);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return string[]
|
|
*/
|
|
public static function sanitize_gallery($value): array {
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
$gallery = [];
|
|
foreach ($value as $url) {
|
|
$url = esc_url_raw((string) $url);
|
|
if ($url !== '') {
|
|
$gallery[] = $url;
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique($gallery));
|
|
}
|
|
|
|
/**
|
|
* Sanitize card/key codes for auto-delivery.
|
|
* Each entry is a non-empty sanitized string (one code per line).
|
|
*/
|
|
public static function sanitize_card_keys($value): array {
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
$keys = [];
|
|
foreach ($value as $key) {
|
|
$key = sanitize_text_field((string) $key);
|
|
if ($key !== '') {
|
|
$keys[] = $key;
|
|
}
|
|
}
|
|
|
|
return $keys;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $raw
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function sanitize_pay_meta(array $raw): array {
|
|
$meta = self::default_pay_meta();
|
|
$meta['pay_type'] = self::normalize_pay_type((string) ($raw['pay_type'] ?? $meta['pay_type']));
|
|
$pay_modo = sanitize_key((string) ($raw['pay_modo'] ?? '0'));
|
|
$meta['pay_modo'] = $pay_modo === 'points' ? 'points' : '0';
|
|
$meta['price'] = max(0, (float) ($raw['price'] ?? 0));
|
|
$meta['points_price'] = max(0, (int) ($raw['points_price'] ?? $raw['price'] ?? 0));
|
|
$meta['original_price'] = max(0, (float) ($raw['original_price'] ?? 0));
|
|
$meta['vip1_price'] = max(0, (float) ($raw['vip1_price'] ?? 0));
|
|
$meta['vip2_price'] = max(0, (float) ($raw['vip2_price'] ?? 0));
|
|
$meta['vip1_points'] = max(0, (int) ($raw['vip1_points'] ?? $raw['vip1_price'] ?? 0));
|
|
$meta['vip2_points'] = max(0, (int) ($raw['vip2_points'] ?? $raw['vip2_price'] ?? 0));
|
|
$pay_limit = (int) ($raw['pay_limit'] ?? 0);
|
|
$meta['pay_limit'] = in_array($pay_limit, [0, 1, 2], true) ? $pay_limit : 0;
|
|
$meta['version'] = sanitize_text_field((string) ($raw['version'] ?? ''));
|
|
$meta['size'] = sanitize_text_field((string) ($raw['size'] ?? ''));
|
|
$meta['license'] = sanitize_text_field((string) ($raw['license'] ?? ''));
|
|
$meta['compat'] = sanitize_text_field((string) ($raw['compat'] ?? ''));
|
|
$meta['demo_url'] = esc_url_raw((string) ($raw['demo_url'] ?? ''));
|
|
$meta['video_url'] = esc_url_raw((string) ($raw['video_url'] ?? ''));
|
|
$meta['downloads'] = self::sanitize_downloads($raw['downloads'] ?? []);
|
|
$meta['attributes'] = self::sanitize_attributes($raw['attributes'] ?? []);
|
|
$meta['gallery'] = self::sanitize_gallery($raw['gallery'] ?? []);
|
|
$meta['require_login'] = !empty($raw['require_login']);
|
|
$meta['require_captcha'] = !empty($raw['require_captcha']);
|
|
|
|
// §3.8 电商增强字段
|
|
$meta['virtual_sales'] = max(0, (int) ($raw['virtual_sales'] ?? 0));
|
|
$meta['stock'] = (int) ($raw['stock'] ?? -1);
|
|
$meta['card_keys'] = self::sanitize_card_keys($raw['card_keys'] ?? []);
|
|
$meta['purchase_limit'] = max(0, (int) ($raw['purchase_limit'] ?? 0));
|
|
$meta['free_preview'] = wp_kses_post((string) ($raw['free_preview'] ?? ''));
|
|
$meta['show_sales'] = !isset($raw['show_sales']) || !empty($raw['show_sales']);
|
|
$meta['download_limit_over_price'] = max(0, (float) ($raw['download_limit_over_price'] ?? 0));
|
|
|
|
return $meta;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function order_status_labels(): array {
|
|
return [
|
|
self::ORDER_STATUS_CREATED => __('已创建', 'aksrt'),
|
|
self::ORDER_STATUS_PENDING => __('待支付', 'aksrt'),
|
|
self::ORDER_STATUS_PAID => __('已支付', 'aksrt'),
|
|
self::ORDER_STATUS_DELIVERED => __('已发货', 'aksrt'),
|
|
self::ORDER_STATUS_COMPLETED => __('已完成', 'aksrt'),
|
|
self::ORDER_STATUS_CANCELLED => __('已取消', 'aksrt'),
|
|
self::ORDER_STATUS_EXPIRED => __('已过期', 'aksrt'),
|
|
self::ORDER_STATUS_REFUNDING => __('退款中', 'aksrt'),
|
|
self::ORDER_STATUS_REFUNDED => __('已退款', 'aksrt'),
|
|
self::ORDER_STATUS_PENDING_REVIEW => __('待评价', 'aksrt'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string[]>
|
|
*/
|
|
public static function order_transitions(): array {
|
|
return [
|
|
self::ORDER_STATUS_CREATED => [
|
|
self::ORDER_STATUS_PENDING,
|
|
self::ORDER_STATUS_CANCELLED,
|
|
],
|
|
self::ORDER_STATUS_PENDING => [
|
|
self::ORDER_STATUS_PAID,
|
|
self::ORDER_STATUS_CANCELLED,
|
|
self::ORDER_STATUS_EXPIRED,
|
|
],
|
|
self::ORDER_STATUS_PAID => [
|
|
self::ORDER_STATUS_DELIVERED,
|
|
self::ORDER_STATUS_COMPLETED,
|
|
self::ORDER_STATUS_REFUNDING,
|
|
],
|
|
self::ORDER_STATUS_DELIVERED => [
|
|
self::ORDER_STATUS_COMPLETED,
|
|
self::ORDER_STATUS_PENDING_REVIEW,
|
|
self::ORDER_STATUS_REFUNDING,
|
|
],
|
|
self::ORDER_STATUS_COMPLETED => [
|
|
self::ORDER_STATUS_PENDING_REVIEW,
|
|
self::ORDER_STATUS_REFUNDING,
|
|
],
|
|
self::ORDER_STATUS_CANCELLED => [],
|
|
self::ORDER_STATUS_EXPIRED => [
|
|
self::ORDER_STATUS_PENDING,
|
|
],
|
|
self::ORDER_STATUS_REFUNDING => [
|
|
self::ORDER_STATUS_REFUNDED,
|
|
self::ORDER_STATUS_PAID,
|
|
],
|
|
self::ORDER_STATUS_REFUNDED => [],
|
|
self::ORDER_STATUS_PENDING_REVIEW => [
|
|
self::ORDER_STATUS_COMPLETED,
|
|
self::ORDER_STATUS_REFUNDING,
|
|
],
|
|
];
|
|
}
|
|
|
|
public static function normalize_order_status(string $status, string $default = self::ORDER_STATUS_PENDING): string {
|
|
$status = sanitize_key($status);
|
|
return array_key_exists($status, self::order_status_labels()) ? $status : $default;
|
|
}
|
|
|
|
public static function can_transition_order_status(string $from, string $to): bool {
|
|
$from = self::normalize_order_status($from, self::ORDER_STATUS_CREATED);
|
|
$to = self::normalize_order_status($to);
|
|
$transitions = self::order_transitions();
|
|
|
|
return isset($transitions[$from]) && in_array($to, $transitions[$from], true);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public static function paid_order_statuses(): array {
|
|
return [
|
|
self::ORDER_STATUS_PAID,
|
|
self::ORDER_STATUS_DELIVERED,
|
|
self::ORDER_STATUS_COMPLETED,
|
|
];
|
|
}
|
|
|
|
public static function is_paid_order_status(string $status): bool {
|
|
return in_array(self::normalize_order_status($status), self::paid_order_statuses(), true);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public static function vip_active_order_statuses(): array {
|
|
return self::paid_order_statuses();
|
|
}
|
|
}
|