Merge pull request 'feat(payment): 补齐创作分成结算 + 收尾 auth 壳层欠账' (#76) from worktree-deferred-debt-sweep into main
Reviewed-on: CYL/wordpress-theme-aksrt#76
This commit is contained in:
commit
0941c7d23f
5 files changed
+163
-3
No files matched your search
@@ -168,6 +168,9 @@ if (class_exists('AKSRT_Signin')) {
|
|||||||
if (class_exists('AKSRT_Invite')) {
|
if (class_exists('AKSRT_Invite')) {
|
||||||
AKSRT_Invite::get_instance()->init();
|
AKSRT_Invite::get_instance()->init();
|
||||||
}
|
}
|
||||||
|
if (class_exists('AKSRT_Creator_Revenue')) {
|
||||||
|
AKSRT_Creator_Revenue::get_instance()->init();
|
||||||
|
}
|
||||||
if (class_exists('AKSRT_Download')) {
|
if (class_exists('AKSRT_Download')) {
|
||||||
AKSRT_Download::get_instance()->init();
|
AKSRT_Download::get_instance()->init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,6 +276,9 @@ require_once AKSRT_DIR . '/inc/classes/gateways/class-aksrt-stripe-gateway.php';
|
|||||||
// 收益台账
|
// 收益台账
|
||||||
require_once AKSRT_DIR . '/inc/classes/class-aksrt-revenue.php';
|
require_once AKSRT_DIR . '/inc/classes/class-aksrt-revenue.php';
|
||||||
|
|
||||||
|
// 创作分成结算(监听 aksrt_order_paid,把作者分成写入收益台账)
|
||||||
|
require_once AKSRT_DIR . '/inc/classes/class-aksrt-creator-revenue.php';
|
||||||
|
|
||||||
// 提现台账
|
// 提现台账
|
||||||
require_once AKSRT_DIR . '/inc/classes/class-aksrt-withdrawal.php';
|
require_once AKSRT_DIR . '/inc/classes/class-aksrt-withdrawal.php';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AKSRT Creator Revenue Settlement.
|
||||||
|
*
|
||||||
|
* Wires order-paid + non-shop paid content into the pre-existing revenue ledger
|
||||||
|
* (`aksrt_revenue` table + AKSRT_Revenue service). This closes the deferred
|
||||||
|
* "结算引擎" gap: the ledger, service and share-ratio settings already existed
|
||||||
|
* but nothing credited creator revenue when an order was paid.
|
||||||
|
*
|
||||||
|
* Scope (kept intentionally narrow):
|
||||||
|
* - Only shop-product orders and post-level paid content credit revenue.
|
||||||
|
* - Share ratio comes from AKSRT_Settings::get_payment_revenue_settings().
|
||||||
|
* - Buyer == author is skipped (no self-dealing).
|
||||||
|
* - Idempotent per order_number via a guard row lookup.
|
||||||
|
*
|
||||||
|
* @package AKSRT
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
if (!class_exists('AKSRT_Creator_Revenue')) {
|
||||||
|
|
||||||
|
class AKSRT_Creator_Revenue {
|
||||||
|
|
||||||
|
private static ?AKSRT_Creator_Revenue $instance = null;
|
||||||
|
|
||||||
|
public static function get_instance(): self {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init(): void {
|
||||||
|
add_action('aksrt_order_paid', [$this, 'handle_order_paid'], 20, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether creator revenue settlement is enabled.
|
||||||
|
*/
|
||||||
|
private function is_enabled(): bool {
|
||||||
|
if (!class_exists('AKSRT_Revenue') || !class_exists('AKSRT_Settings')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$settings = AKSRT_Settings::get_payment_revenue_settings();
|
||||||
|
return !empty($settings['enabled']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve share ratio (percentage 0-100) for a given product author.
|
||||||
|
*/
|
||||||
|
private function resolve_share_ratio(int $author_id): float {
|
||||||
|
$settings = AKSRT_Settings::get_payment_revenue_settings();
|
||||||
|
$ratio = (float) ($settings['share_ratio'] ?? 0);
|
||||||
|
|
||||||
|
// VIP-tier author overrides, if configured (>0 wins).
|
||||||
|
if ($author_id > 0 && function_exists('aksrt_get_user_vip_level')) {
|
||||||
|
$level = (int) aksrt_get_user_vip_level($author_id);
|
||||||
|
if ($level === 1 && (float) ($settings['ratio_vip1'] ?? 0) > 0) {
|
||||||
|
$ratio = (float) $settings['ratio_vip1'];
|
||||||
|
} elseif ($level >= 2 && (float) ($settings['ratio_vip2'] ?? 0) > 0) {
|
||||||
|
$ratio = (float) $settings['ratio_vip2'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0.0, min(100.0, $ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guard: has this order already been settled into revenue?
|
||||||
|
*/
|
||||||
|
private function already_settled(string $order_number): bool {
|
||||||
|
if ($order_number === '' || !class_exists('AKSRT_DB_Migrator')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
global $wpdb;
|
||||||
|
$table = AKSRT_DB_Migrator::table('revenue');
|
||||||
|
if ($table === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$count = (int) $wpdb->get_var($wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM {$table} WHERE order_number = %s AND type = %s",
|
||||||
|
$order_number,
|
||||||
|
'order'
|
||||||
|
));
|
||||||
|
return $count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aksrt_order_paid listener: credit creators their share.
|
||||||
|
*
|
||||||
|
* @param string $order_number Paid order number.
|
||||||
|
* @param array $order Order row (status=paid).
|
||||||
|
*/
|
||||||
|
public function handle_order_paid(string $order_number, array $order): void {
|
||||||
|
if (!$this->is_enabled() || $order_number === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->already_settled($order_number)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buyer_id = (int) ($order['user_id'] ?? 0);
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
if (class_exists('AKSRT_Shop')) {
|
||||||
|
$shop = AKSRT_Shop::get_instance();
|
||||||
|
if (method_exists($shop, 'get_order_items')) {
|
||||||
|
$items = (array) $shop->get_order_items($order_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($items)) {
|
||||||
|
return; // Only structured shop-product orders settle creator revenue here.
|
||||||
|
}
|
||||||
|
|
||||||
|
$revenue = AKSRT_Revenue::get_instance();
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$product_id = (int) ($item['product_id'] ?? 0);
|
||||||
|
$pay_price = (float) ($item['pay_price'] ?? $item['total_price'] ?? 0);
|
||||||
|
if ($product_id <= 0 || $pay_price <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$author_id = (int) get_post_field('post_author', $product_id);
|
||||||
|
if ($author_id <= 0 || $author_id === $buyer_id) {
|
||||||
|
continue; // No author, or buyer is the author.
|
||||||
|
}
|
||||||
|
|
||||||
|
$ratio = $this->resolve_share_ratio($author_id);
|
||||||
|
if ($ratio <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = round($pay_price * $ratio / 100, 2);
|
||||||
|
if ($amount <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$revenue->add($author_id, $amount, $order_number, 'order');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,13 +49,19 @@ $verify_mode = (string) ($verify_context['mode'] ?? 'invalid');
|
|||||||
$verify_method= (string) ($verify_context['method'] ?? '');
|
$verify_method= (string) ($verify_context['method'] ?? '');
|
||||||
$verify_vk = (string) ($verify_context['access_key'] ?? '');
|
$verify_vk = (string) ($verify_context['access_key'] ?? '');
|
||||||
|
|
||||||
|
remove_action('wp_footer', 'aksrt_render_footer_tabbar', 20);
|
||||||
|
remove_action('wp_footer', 'aksrt_render_float_buttons', 30);
|
||||||
|
add_filter('body_class', static function ($classes) {
|
||||||
|
$classes[] = 'page-template-user-sign';
|
||||||
|
return $classes;
|
||||||
|
});
|
||||||
get_header();
|
get_header();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<main id="main-content" class="site-main" tabindex="-1">
|
<main id="main-content" class="site-main sign-page" tabindex="-1">
|
||||||
<div class="auth-shell">
|
<div class="auth-shell">
|
||||||
<div class="auth-card">
|
<div class="auth-card sign-card sign-card--center">
|
||||||
<div class="auth-card__logo" aria-hidden="true">AK</div>
|
<div class="auth-card__logo sign-logo" aria-hidden="true"><?php if (function_exists('aksrt_get_auth_sign_logo_html')) { echo aksrt_get_auth_sign_logo_html(); } else { echo 'AK'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||||
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
||||||
|
|
||||||
<?php if ($verify_mode === 'completed' || $verify_mode === 'verified') : ?>
|
<?php if ($verify_mode === 'completed' || $verify_mode === 'verified') : ?>
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ $show_qrcode_tab = false;
|
|||||||
|
|
||||||
if (class_exists('AKSRT_Settings')) {
|
if (class_exists('AKSRT_Settings')) {
|
||||||
$registration_open = AKSRT_Settings::registration_enabled();
|
$registration_open = AKSRT_Settings::registration_enabled();
|
||||||
|
if (method_exists('AKSRT_Settings', 'close_signup') && AKSRT_Settings::close_signup()) {
|
||||||
|
$registration_open = false;
|
||||||
|
}
|
||||||
$email_verification = AKSRT_Settings::registration_requires_email_verification();
|
$email_verification = AKSRT_Settings::registration_requires_email_verification();
|
||||||
$close_signin = AKSRT_Settings::close_signin();
|
$close_signin = AKSRT_Settings::close_signin();
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user