refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
1 parent
56f1a8175f
commit
8c5b1555c9
285 files changed
+26
-3600
No files matched your search
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class CreatorRevenue {
|
||||
|
||||
private static ?CreatorRevenue $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\\Payment\\Revenue') || !class_exists('\AKSRT\Settings\Settings')) {
|
||||
return false;
|
||||
}
|
||||
$settings = \AKSRT\Settings\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\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\Core\DBMigrator')) {
|
||||
return false;
|
||||
}
|
||||
global $wpdb;
|
||||
$table = \AKSRT\Core\DBMigrator::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);
|
||||
|
||||
$this->settle_non_shop_cash_order($order_number, $order, $buyer_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content / topic cash orders (no shop line items).
|
||||
*/
|
||||
private function settle_non_shop_cash_order(string $order_number, array $order, int $buyer_id): void {
|
||||
$meta = $this->decode_note($order);
|
||||
if (($meta['pay_modo'] ?? '') === 'points') {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key((string) ($order['payment_method'] ?? ''));
|
||||
$currency = sanitize_key((string) ($order['currency'] ?? ''));
|
||||
if ($payment_method === 'points' || $currency === 'points') {
|
||||
return;
|
||||
}
|
||||
|
||||
$base_amount = round((float) ($order['amount'] ?? $meta['payable'] ?? 0), 2);
|
||||
if ($base_amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_type = sanitize_key((string) ($order['product_type'] ?? ''));
|
||||
$product_id = (int) ($order['product_id'] ?? 0);
|
||||
if ($product_id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = get_post($product_id);
|
||||
if (!($post instanceof \WP_Post)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_content = !empty($meta['content_pay'])
|
||||
&& $post->post_type === 'post';
|
||||
|
||||
// Resource without content_pay and not a known cash kind: skip.
|
||||
if (!$is_content) {
|
||||
return;
|
||||
}
|
||||
|
||||
$author_id = (int) $post->post_author;
|
||||
if ($author_id <= 0 || $author_id === $buyer_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ratio = $this->resolve_share_ratio($author_id);
|
||||
if ($ratio <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$amount = round($base_amount * $ratio / 100, 2);
|
||||
if ($amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-check after shop-less path; shop path already returned.
|
||||
if ($this->already_settled($order_number)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Revenue::get_instance()->add($author_id, $amount, $order_number, 'order');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $order
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function decode_note(array $order): array {
|
||||
if (isset($order['meta_payload']) && is_array($order['meta_payload'])) {
|
||||
return $order['meta_payload'];
|
||||
}
|
||||
$note = (string) ($order['note'] ?? '');
|
||||
if ($note === '') {
|
||||
return [];
|
||||
}
|
||||
$decoded = json_decode($note, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user