🔧 Bug Fixes: - Fixed download limits defaulting to 5 instead of 0 for unlimited downloads - Fixed software license filename sanitization (spaces→dashes, dots→underscores, proper .zip extension) - Software downloads now show as "Test-Plugin-v2-2-0.zip" instead of "Test Plugin v2.2.0" ✨ UI/UX Enhancements: - Redesigned license key display to span full table width with FontAwesome copy icons - Added responsive CSS styling for license key rows - Integrated FontAwesome CDN for modern copy icons 🏗️ Architecture Improvements: - Added comprehensive filename sanitization in both download handler and API paths - Enhanced software license product handling for local package files - Improved error handling and logging throughout download processes 📦 Infrastructure: - Added Gitea workflows for automated releases on push to main - Created comprehensive .gitignore excluding test files and browser automation - Updated documentation with all recent improvements and technical insights 🔍 Technical Details: - Software license products served from wp-content/uploads/wpdd-packages/ - Download flow: token → process_download_by_token() → process_download() → deliver_file() - Dual path coverage for both API downloads and regular file delivery - Version placeholder system for automated deployment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
781 lines
40 KiB
PHP
781 lines
40 KiB
PHP
<?php
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class WPDD_Transaction_History {
|
|
|
|
public static function init() {
|
|
add_action('admin_menu', array(__CLASS__, 'add_menu_page'));
|
|
add_action('admin_post_wpdd_export_transactions', array(__CLASS__, 'handle_export'));
|
|
add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_scripts'));
|
|
}
|
|
|
|
public static function add_menu_page() {
|
|
// Add for admins under Digital Products
|
|
if (current_user_can('manage_options')) {
|
|
add_submenu_page(
|
|
'edit.php?post_type=wpdd_product',
|
|
__('Transaction History', 'wp-digital-download'),
|
|
__('Transactions', 'wp-digital-download'),
|
|
'manage_options',
|
|
'wpdd-transactions',
|
|
array(__CLASS__, 'render_admin_page')
|
|
);
|
|
}
|
|
|
|
// Add for creators (they can only see their own)
|
|
if (current_user_can('wpdd_creator') && !current_user_can('manage_options')) {
|
|
add_menu_page(
|
|
__('My Transactions', 'wp-digital-download'),
|
|
__('My Transactions', 'wp-digital-download'),
|
|
'wpdd_creator',
|
|
'wpdd-my-transactions',
|
|
array(__CLASS__, 'render_creator_page'),
|
|
'dashicons-money-alt',
|
|
30
|
|
);
|
|
}
|
|
}
|
|
|
|
public static function enqueue_scripts($hook) {
|
|
if (!in_array($hook, array('wpdd_product_page_wpdd-transactions', 'toplevel_page_wpdd-my-transactions'))) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_script('jquery-ui-datepicker');
|
|
wp_enqueue_style('jquery-ui', 'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');
|
|
}
|
|
|
|
public static function render_admin_page() {
|
|
global $wpdb;
|
|
|
|
// Get filter parameters
|
|
$creator_id = isset($_GET['creator_id']) ? intval($_GET['creator_id']) : 0;
|
|
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-6 months'));
|
|
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
|
|
$type_filter = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : 'all';
|
|
|
|
// Get all creators for dropdown
|
|
$creators = get_users(array('role' => 'wpdd_creator'));
|
|
|
|
// If creator is selected, show their transactions
|
|
if ($creator_id) {
|
|
self::render_transactions($creator_id, $date_from, $date_to, $type_filter, true);
|
|
} else {
|
|
?>
|
|
<div class="wrap">
|
|
<h1><?php _e('Transaction History', 'wp-digital-download'); ?></h1>
|
|
|
|
<div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
|
<form method="get" action="">
|
|
<input type="hidden" name="post_type" value="wpdd_product">
|
|
<input type="hidden" name="page" value="wpdd-transactions">
|
|
|
|
<table class="form-table">
|
|
<tr>
|
|
<th><label for="creator_id"><?php _e('Select Creator', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<select name="creator_id" id="creator_id" required>
|
|
<option value=""><?php _e('-- Select Creator --', 'wp-digital-download'); ?></option>
|
|
<?php foreach ($creators as $creator) :
|
|
$balance = WPDD_Creator::get_creator_balance($creator->ID);
|
|
$total_earnings = WPDD_Creator::get_creator_total_earnings($creator->ID);
|
|
?>
|
|
<option value="<?php echo esc_attr($creator->ID); ?>" <?php selected($creator_id, $creator->ID); ?>>
|
|
<?php echo esc_html($creator->display_name); ?>
|
|
(<?php echo esc_html($creator->user_email); ?>) -
|
|
<?php printf(__('Balance: %s, Total: %s', 'wp-digital-download'),
|
|
wpdd_format_price($balance),
|
|
wpdd_format_price($total_earnings)); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th><label for="date_from"><?php _e('Date Range', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
|
|
<?php _e('to', 'wp-digital-download'); ?>
|
|
<input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th><label for="type"><?php _e('Transaction Type', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<select name="type" id="type">
|
|
<option value="all"><?php _e('All Transactions', 'wp-digital-download'); ?></option>
|
|
<option value="earnings" <?php selected($type_filter, 'earnings'); ?>><?php _e('Earnings Only', 'wp-digital-download'); ?></option>
|
|
<option value="payouts" <?php selected($type_filter, 'payouts'); ?>><?php _e('Payouts Only', 'wp-digital-download'); ?></option>
|
|
<option value="adjustments" <?php selected($type_filter, 'adjustments'); ?>><?php _e('Adjustments Only', 'wp-digital-download'); ?></option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p class="submit">
|
|
<input type="submit" class="button button-primary" value="<?php _e('View Transactions', 'wp-digital-download'); ?>">
|
|
</p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
public static function render_creator_page() {
|
|
$creator_id = get_current_user_id();
|
|
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-6 months'));
|
|
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
|
|
$type_filter = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : 'all';
|
|
|
|
self::render_transactions($creator_id, $date_from, $date_to, $type_filter, false);
|
|
}
|
|
|
|
private static function render_transactions($creator_id, $date_from, $date_to, $type_filter, $is_admin) {
|
|
global $wpdb;
|
|
|
|
$creator = get_userdata($creator_id);
|
|
if (!$creator) {
|
|
echo '<div class="notice notice-error"><p>' . __('Creator not found.', 'wp-digital-download') . '</p></div>';
|
|
return;
|
|
}
|
|
|
|
// Fetch all transactions for this creator
|
|
$transactions = array();
|
|
|
|
// Get earnings (sales)
|
|
if ($type_filter === 'all' || $type_filter === 'earnings') {
|
|
$earnings = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT e.*, o.order_number, o.customer_name, o.customer_email, p.post_title as product_name
|
|
FROM {$wpdb->prefix}wpdd_creator_earnings e
|
|
LEFT JOIN {$wpdb->prefix}wpdd_orders o ON e.order_id = o.id
|
|
LEFT JOIN {$wpdb->posts} p ON e.product_id = p.ID
|
|
WHERE e.creator_id = %d
|
|
AND DATE(e.created_at) BETWEEN %s AND %s
|
|
ORDER BY e.created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($earnings as $earning) {
|
|
$transactions[] = array(
|
|
'date' => $earning->created_at,
|
|
'type' => 'earning',
|
|
'description' => sprintf(__('Sale: %s to %s', 'wp-digital-download'),
|
|
$earning->product_name, $earning->customer_name),
|
|
'order_number' => $earning->order_number,
|
|
'gross_amount' => $earning->sale_amount,
|
|
'commission' => $earning->sale_amount - $earning->creator_earning,
|
|
'net_amount' => $earning->creator_earning,
|
|
'balance_change' => '+' . $earning->creator_earning,
|
|
'status' => $earning->payout_status,
|
|
'payout_id' => $earning->payout_id
|
|
);
|
|
}
|
|
}
|
|
|
|
// Get payouts
|
|
if ($type_filter === 'all' || $type_filter === 'payouts') {
|
|
$payouts = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_payouts
|
|
WHERE creator_id = %d
|
|
AND DATE(created_at) BETWEEN %s AND %s
|
|
ORDER BY created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($payouts as $payout) {
|
|
$transactions[] = array(
|
|
'date' => $payout->created_at,
|
|
'type' => 'payout',
|
|
'description' => sprintf(__('Payout to %s', 'wp-digital-download'), $payout->paypal_email),
|
|
'order_number' => $payout->transaction_id ?: 'N/A',
|
|
'gross_amount' => 0,
|
|
'commission' => 0,
|
|
'net_amount' => -$payout->amount,
|
|
'balance_change' => '-' . $payout->amount,
|
|
'status' => $payout->status,
|
|
'payout_id' => $payout->id
|
|
);
|
|
}
|
|
}
|
|
|
|
// Get adjustments
|
|
if ($type_filter === 'all' || $type_filter === 'adjustments') {
|
|
$adjustments = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT a.*, u.display_name as adjusted_by_name
|
|
FROM {$wpdb->prefix}wpdd_balance_adjustments a
|
|
LEFT JOIN {$wpdb->users} u ON a.adjusted_by = u.ID
|
|
WHERE a.creator_id = %d
|
|
AND DATE(a.created_at) BETWEEN %s AND %s
|
|
ORDER BY a.created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($adjustments as $adjustment) {
|
|
$amount_change = $adjustment->adjustment_type === 'add' ? $adjustment->amount : -$adjustment->amount;
|
|
$transactions[] = array(
|
|
'date' => $adjustment->created_at,
|
|
'type' => 'adjustment',
|
|
'description' => sprintf(__('Manual adjustment: %s (by %s)', 'wp-digital-download'),
|
|
$adjustment->reason, $adjustment->adjusted_by_name),
|
|
'order_number' => 'ADJ-' . $adjustment->id,
|
|
'gross_amount' => 0,
|
|
'commission' => 0,
|
|
'net_amount' => $amount_change,
|
|
'balance_change' => ($amount_change >= 0 ? '+' : '') . $amount_change,
|
|
'status' => 'completed',
|
|
'payout_id' => null
|
|
);
|
|
}
|
|
}
|
|
|
|
// Sort transactions by date
|
|
usort($transactions, function($a, $b) {
|
|
return strtotime($b['date']) - strtotime($a['date']);
|
|
});
|
|
|
|
// Calculate running balance
|
|
$current_balance = WPDD_Creator::get_creator_balance($creator_id);
|
|
$running_balance = $current_balance;
|
|
|
|
// Calculate balance by going backwards from current
|
|
for ($i = 0; $i < count($transactions); $i++) {
|
|
$transactions[$i]['running_balance'] = $running_balance;
|
|
if ($i < count($transactions) - 1) {
|
|
$running_balance -= floatval(str_replace('+', '', $transactions[$i]['balance_change']));
|
|
}
|
|
}
|
|
|
|
?>
|
|
<div class="wrap">
|
|
<h1>
|
|
<?php
|
|
if ($is_admin) {
|
|
printf(__('Transaction History for %s', 'wp-digital-download'), $creator->display_name);
|
|
} else {
|
|
_e('My Transaction History', 'wp-digital-download');
|
|
}
|
|
?>
|
|
</h1>
|
|
|
|
<div class="wpdd-summary" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
|
<h3><?php _e('Summary', 'wp-digital-download'); ?></h3>
|
|
<?php
|
|
$total_earnings = array_sum(array_column(array_filter($transactions, function($t) {
|
|
return $t['type'] === 'earning';
|
|
}), 'net_amount'));
|
|
$total_payouts = abs(array_sum(array_column(array_filter($transactions, function($t) {
|
|
return $t['type'] === 'payout';
|
|
}), 'net_amount')));
|
|
$total_adjustments = array_sum(array_column(array_filter($transactions, function($t) {
|
|
return $t['type'] === 'adjustment';
|
|
}), 'net_amount'));
|
|
?>
|
|
<table class="widefat">
|
|
<tr>
|
|
<td><strong><?php _e('Period:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php echo esc_html($date_from); ?> to <?php echo esc_html($date_to); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Total Earnings:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php echo wpdd_format_price($total_earnings); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Total Payouts:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php echo wpdd_format_price($total_payouts); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Total Adjustments:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php echo wpdd_format_price($total_adjustments); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong></td>
|
|
<td><strong><?php echo wpdd_format_price($current_balance); ?></strong></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Filter Form -->
|
|
<div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
|
<form method="get" action="">
|
|
<?php if ($is_admin) : ?>
|
|
<input type="hidden" name="post_type" value="wpdd_product">
|
|
<input type="hidden" name="page" value="wpdd-transactions">
|
|
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
|
<?php else : ?>
|
|
<input type="hidden" name="page" value="wpdd-my-transactions">
|
|
<?php endif; ?>
|
|
|
|
<table class="form-table">
|
|
<tr>
|
|
<th><label for="date_from"><?php _e('Date Range', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
|
|
<?php _e('to', 'wp-digital-download'); ?>
|
|
<input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th><label for="type"><?php _e('Transaction Type', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<select name="type" id="type">
|
|
<option value="all"><?php _e('All Transactions', 'wp-digital-download'); ?></option>
|
|
<option value="earnings" <?php selected($type_filter, 'earnings'); ?>><?php _e('Earnings Only', 'wp-digital-download'); ?></option>
|
|
<option value="payouts" <?php selected($type_filter, 'payouts'); ?>><?php _e('Payouts Only', 'wp-digital-download'); ?></option>
|
|
<option value="adjustments" <?php selected($type_filter, 'adjustments'); ?>><?php _e('Adjustments Only', 'wp-digital-download'); ?></option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p class="submit">
|
|
<input type="submit" class="button button-primary" value="<?php _e('Filter', 'wp-digital-download'); ?>">
|
|
</p>
|
|
</form>
|
|
|
|
<!-- Export Forms -->
|
|
<div style="margin-top: 10px;">
|
|
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline-block; margin-right: 10px;">
|
|
<input type="hidden" name="action" value="wpdd_export_transactions">
|
|
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
|
<input type="hidden" name="date_from" value="<?php echo esc_attr($date_from); ?>">
|
|
<input type="hidden" name="date_to" value="<?php echo esc_attr($date_to); ?>">
|
|
<input type="hidden" name="type" value="<?php echo esc_attr($type_filter); ?>">
|
|
<input type="hidden" name="format" value="csv">
|
|
<?php wp_nonce_field('wpdd_export_transactions', 'wpdd_nonce'); ?>
|
|
<button type="submit" class="button">
|
|
<span class="dashicons dashicons-media-spreadsheet"></span> <?php _e('Export CSV', 'wp-digital-download'); ?>
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline-block;" target="_blank">
|
|
<input type="hidden" name="action" value="wpdd_export_transactions">
|
|
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
|
<input type="hidden" name="date_from" value="<?php echo esc_attr($date_from); ?>">
|
|
<input type="hidden" name="date_to" value="<?php echo esc_attr($date_to); ?>">
|
|
<input type="hidden" name="type" value="<?php echo esc_attr($type_filter); ?>">
|
|
<input type="hidden" name="format" value="pdf">
|
|
<?php wp_nonce_field('wpdd_export_transactions', 'wpdd_nonce'); ?>
|
|
<button type="submit" class="button">
|
|
<span class="dashicons dashicons-pdf"></span> <?php _e('Export PDF', 'wp-digital-download'); ?>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transactions Table -->
|
|
<div class="wpdd-transactions-table" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
|
<h3><?php _e('Transactions', 'wp-digital-download'); ?> (<?php echo count($transactions); ?>)</h3>
|
|
|
|
<?php if (empty($transactions)) : ?>
|
|
<p><?php _e('No transactions found for the selected period.', 'wp-digital-download'); ?></p>
|
|
<?php else : ?>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Date', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Type', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Description', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Order/Ref', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Gross', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Commission', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Net', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Balance', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Status', 'wp-digital-download'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($transactions as $transaction) : ?>
|
|
<tr>
|
|
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($transaction['date']))); ?></td>
|
|
<td>
|
|
<?php
|
|
$type_badge = '';
|
|
switch($transaction['type']) {
|
|
case 'earning':
|
|
$type_badge = '<span class="dashicons dashicons-cart" style="color: #28a745;"></span> ' . __('Sale', 'wp-digital-download');
|
|
break;
|
|
case 'payout':
|
|
$type_badge = '<span class="dashicons dashicons-money-alt" style="color: #007cba;"></span> ' . __('Payout', 'wp-digital-download');
|
|
break;
|
|
case 'adjustment':
|
|
$type_badge = '<span class="dashicons dashicons-admin-tools" style="color: #ffc107;"></span> ' . __('Adjustment', 'wp-digital-download');
|
|
break;
|
|
}
|
|
echo $type_badge;
|
|
?>
|
|
</td>
|
|
<td><?php echo esc_html($transaction['description']); ?></td>
|
|
<td><?php echo esc_html($transaction['order_number']); ?></td>
|
|
<td><?php echo $transaction['gross_amount'] > 0 ? wpdd_format_price($transaction['gross_amount']) : '-'; ?></td>
|
|
<td><?php echo $transaction['commission'] > 0 ? wpdd_format_price($transaction['commission']) : '-'; ?></td>
|
|
<td>
|
|
<strong style="color: <?php echo $transaction['net_amount'] >= 0 ? '#28a745' : '#dc3545'; ?>">
|
|
<?php echo ($transaction['net_amount'] >= 0 ? '+' : '') . wpdd_format_price(abs($transaction['net_amount'])); ?>
|
|
</strong>
|
|
</td>
|
|
<td><strong><?php echo wpdd_format_price($transaction['running_balance']); ?></strong></td>
|
|
<td>
|
|
<?php
|
|
$status_class = '';
|
|
$status_text = ucfirst($transaction['status']);
|
|
switch($transaction['status']) {
|
|
case 'paid':
|
|
$status_class = 'notice-success';
|
|
$status_text = __('Paid', 'wp-digital-download');
|
|
if ($transaction['payout_id']) {
|
|
$status_text .= ' (#' . $transaction['payout_id'] . ')';
|
|
}
|
|
break;
|
|
case 'unpaid':
|
|
$status_class = 'notice-warning';
|
|
$status_text = __('Unpaid', 'wp-digital-download');
|
|
break;
|
|
case 'completed':
|
|
$status_class = 'notice-success';
|
|
$status_text = __('Completed', 'wp-digital-download');
|
|
break;
|
|
case 'pending':
|
|
$status_class = 'notice-warning';
|
|
$status_text = __('Pending', 'wp-digital-download');
|
|
break;
|
|
case 'failed':
|
|
$status_class = 'notice-error';
|
|
$status_text = __('Failed', 'wp-digital-download');
|
|
break;
|
|
}
|
|
?>
|
|
<span class="notice <?php echo esc_attr($status_class); ?>" style="padding: 2px 8px; display: inline-block;">
|
|
<?php echo esc_html($status_text); ?>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
public static function handle_export() {
|
|
// Verify nonce
|
|
if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_export_transactions')) {
|
|
wp_die(__('Security check failed', 'wp-digital-download'));
|
|
}
|
|
|
|
// Check permissions
|
|
$creator_id = intval($_POST['creator_id']);
|
|
if (!current_user_can('manage_options') && get_current_user_id() !== $creator_id) {
|
|
wp_die(__('Permission denied', 'wp-digital-download'));
|
|
}
|
|
|
|
$date_from = sanitize_text_field($_POST['date_from']);
|
|
$date_to = sanitize_text_field($_POST['date_to']);
|
|
$type_filter = sanitize_text_field($_POST['type']);
|
|
$format = sanitize_text_field($_POST['format']);
|
|
|
|
// Get transactions data (same logic as render_transactions but just data)
|
|
$transactions = self::get_transactions_data($creator_id, $date_from, $date_to, $type_filter);
|
|
|
|
self::handle_export_request($creator_id, $transactions, $date_from, $date_to, $format);
|
|
}
|
|
|
|
private static function get_transactions_data($creator_id, $date_from, $date_to, $type_filter) {
|
|
global $wpdb;
|
|
|
|
$transactions = array();
|
|
|
|
// Get earnings (sales)
|
|
if ($type_filter === 'all' || $type_filter === 'earnings') {
|
|
$earnings = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT e.*, o.order_number, o.customer_name, o.customer_email, p.post_title as product_name
|
|
FROM {$wpdb->prefix}wpdd_creator_earnings e
|
|
LEFT JOIN {$wpdb->prefix}wpdd_orders o ON e.order_id = o.id
|
|
LEFT JOIN {$wpdb->posts} p ON e.product_id = p.ID
|
|
WHERE e.creator_id = %d
|
|
AND DATE(e.created_at) BETWEEN %s AND %s
|
|
ORDER BY e.created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($earnings as $earning) {
|
|
$transactions[] = array(
|
|
'date' => $earning->created_at,
|
|
'type' => 'earning',
|
|
'description' => sprintf(__('Sale: %s to %s', 'wp-digital-download'),
|
|
$earning->product_name, $earning->customer_name),
|
|
'order_number' => $earning->order_number,
|
|
'gross_amount' => $earning->sale_amount,
|
|
'commission' => $earning->sale_amount - $earning->creator_earning,
|
|
'net_amount' => $earning->creator_earning,
|
|
'balance_change' => '+' . $earning->creator_earning,
|
|
'status' => $earning->payout_status,
|
|
'payout_id' => $earning->payout_id
|
|
);
|
|
}
|
|
}
|
|
|
|
// Get payouts
|
|
if ($type_filter === 'all' || $type_filter === 'payouts') {
|
|
$payouts = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_payouts
|
|
WHERE creator_id = %d
|
|
AND DATE(created_at) BETWEEN %s AND %s
|
|
ORDER BY created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($payouts as $payout) {
|
|
$transactions[] = array(
|
|
'date' => $payout->created_at,
|
|
'type' => 'payout',
|
|
'description' => sprintf(__('Payout to %s', 'wp-digital-download'), $payout->paypal_email),
|
|
'order_number' => $payout->transaction_id ?: 'N/A',
|
|
'gross_amount' => 0,
|
|
'commission' => 0,
|
|
'net_amount' => -$payout->amount,
|
|
'balance_change' => '-' . $payout->amount,
|
|
'status' => $payout->status,
|
|
'payout_id' => $payout->id
|
|
);
|
|
}
|
|
}
|
|
|
|
// Get adjustments
|
|
if ($type_filter === 'all' || $type_filter === 'adjustments') {
|
|
$adjustments = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT a.*, u.display_name as adjusted_by_name
|
|
FROM {$wpdb->prefix}wpdd_balance_adjustments a
|
|
LEFT JOIN {$wpdb->users} u ON a.adjusted_by = u.ID
|
|
WHERE a.creator_id = %d
|
|
AND DATE(a.created_at) BETWEEN %s AND %s
|
|
ORDER BY a.created_at DESC",
|
|
$creator_id, $date_from, $date_to
|
|
));
|
|
|
|
foreach ($adjustments as $adjustment) {
|
|
$amount_change = $adjustment->adjustment_type === 'add' ? $adjustment->amount : -$adjustment->amount;
|
|
$transactions[] = array(
|
|
'date' => $adjustment->created_at,
|
|
'type' => 'adjustment',
|
|
'description' => sprintf(__('Manual adjustment: %s (by %s)', 'wp-digital-download'),
|
|
$adjustment->reason, $adjustment->adjusted_by_name),
|
|
'order_number' => 'ADJ-' . $adjustment->id,
|
|
'gross_amount' => 0,
|
|
'commission' => 0,
|
|
'net_amount' => $amount_change,
|
|
'balance_change' => ($amount_change >= 0 ? '+' : '') . $amount_change,
|
|
'status' => 'completed',
|
|
'payout_id' => null
|
|
);
|
|
}
|
|
}
|
|
|
|
// Sort transactions by date
|
|
usort($transactions, function($a, $b) {
|
|
return strtotime($b['date']) - strtotime($a['date']);
|
|
});
|
|
|
|
// Calculate running balance
|
|
$current_balance = WPDD_Creator::get_creator_balance($creator_id);
|
|
$running_balance = $current_balance;
|
|
|
|
// Calculate balance by going backwards from current
|
|
for ($i = 0; $i < count($transactions); $i++) {
|
|
$transactions[$i]['running_balance'] = $running_balance;
|
|
if ($i < count($transactions) - 1) {
|
|
$running_balance -= floatval(str_replace('+', '', $transactions[$i]['balance_change']));
|
|
}
|
|
}
|
|
|
|
return $transactions;
|
|
}
|
|
|
|
private static function handle_export_request($creator_id, $transactions, $date_from, $date_to, $format) {
|
|
$creator = get_userdata($creator_id);
|
|
|
|
if ($format === 'csv') {
|
|
self::export_csv($transactions, $creator, $date_from, $date_to);
|
|
} elseif ($format === 'pdf') {
|
|
self::export_pdf($transactions, $creator, $date_from, $date_to);
|
|
}
|
|
}
|
|
|
|
private static function export_csv($transactions, $creator, $date_from, $date_to) {
|
|
$filename = sprintf('transactions_%s_%s_to_%s.csv',
|
|
sanitize_title($creator->display_name),
|
|
$date_from,
|
|
$date_to
|
|
);
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename=' . $filename);
|
|
|
|
$output = fopen('php://output', 'w');
|
|
|
|
// Add BOM for Excel UTF-8 compatibility
|
|
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
|
|
|
|
// Header row
|
|
fputcsv($output, array(
|
|
'Date',
|
|
'Type',
|
|
'Description',
|
|
'Order/Reference',
|
|
'Gross Amount',
|
|
'Commission',
|
|
'Net Amount',
|
|
'Running Balance',
|
|
'Status'
|
|
));
|
|
|
|
// Data rows
|
|
foreach ($transactions as $transaction) {
|
|
fputcsv($output, array(
|
|
date('Y-m-d H:i:s', strtotime($transaction['date'])),
|
|
ucfirst($transaction['type']),
|
|
$transaction['description'],
|
|
$transaction['order_number'],
|
|
$transaction['gross_amount'] > 0 ? number_format($transaction['gross_amount'], 2) : '',
|
|
$transaction['commission'] > 0 ? number_format($transaction['commission'], 2) : '',
|
|
number_format(abs($transaction['net_amount']), 2) . ($transaction['net_amount'] < 0 ? ' (Debit)' : ''),
|
|
number_format($transaction['running_balance'], 2),
|
|
ucfirst($transaction['status'])
|
|
));
|
|
}
|
|
|
|
fclose($output);
|
|
exit;
|
|
}
|
|
|
|
private static function export_pdf($transactions, $creator, $date_from, $date_to) {
|
|
// Create a clean PDF-ready HTML document
|
|
$filename = sprintf('transactions_%s_%s_to_%s.pdf',
|
|
sanitize_title($creator->display_name),
|
|
$date_from,
|
|
$date_to
|
|
);
|
|
|
|
// Set headers for PDF display/download
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
header('Content-Disposition: inline; filename=' . $filename);
|
|
header('X-Robots-Tag: noindex, nofollow');
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title><?php printf(__('Transaction History - %s', 'wp-digital-download'), $creator->display_name); ?></title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; font-size: 12px; }
|
|
h1 { font-size: 18px; }
|
|
h2 { font-size: 14px; margin-top: 20px; }
|
|
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
th { background-color: #f2f2f2; font-weight: bold; }
|
|
.summary { margin: 20px 0; }
|
|
.summary td { border: none; padding: 5px 0; }
|
|
.positive { color: green; }
|
|
.negative { color: red; }
|
|
@media print {
|
|
body { font-size: 10px; }
|
|
h1 { font-size: 14px; }
|
|
th, td { padding: 4px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1><?php printf(__('Transaction History - %s', 'wp-digital-download'), $creator->display_name); ?></h1>
|
|
<p><?php printf(__('Period: %s to %s', 'wp-digital-download'), $date_from, $date_to); ?></p>
|
|
|
|
<div class="summary">
|
|
<h2><?php _e('Summary', 'wp-digital-download'); ?></h2>
|
|
<table>
|
|
<tr>
|
|
<td><strong><?php _e('Total Earnings:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php
|
|
$total_earnings = array_sum(array_column(array_filter($transactions, function($t) {
|
|
return $t['type'] === 'earning';
|
|
}), 'net_amount'));
|
|
echo wpdd_format_price($total_earnings);
|
|
?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Total Payouts:', 'wp-digital-download'); ?></strong></td>
|
|
<td><?php
|
|
$total_payouts = abs(array_sum(array_column(array_filter($transactions, function($t) {
|
|
return $t['type'] === 'payout';
|
|
}), 'net_amount')));
|
|
echo wpdd_format_price($total_payouts);
|
|
?></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong></td>
|
|
<td><strong><?php echo wpdd_format_price(WPDD_Creator::get_creator_balance($creator->ID)); ?></strong></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<h2><?php _e('Transaction Details', 'wp-digital-download'); ?></h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Date', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Type', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Description', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Order/Ref', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Net Amount', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Balance', 'wp-digital-download'); ?></th>
|
|
<th><?php _e('Status', 'wp-digital-download'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($transactions as $transaction) : ?>
|
|
<tr>
|
|
<td><?php echo date('Y-m-d H:i', strtotime($transaction['date'])); ?></td>
|
|
<td><?php echo ucfirst($transaction['type']); ?></td>
|
|
<td><?php echo esc_html($transaction['description']); ?></td>
|
|
<td><?php echo esc_html($transaction['order_number']); ?></td>
|
|
<td class="<?php echo $transaction['net_amount'] >= 0 ? 'positive' : 'negative'; ?>">
|
|
<?php echo ($transaction['net_amount'] >= 0 ? '+' : '') . wpdd_format_price(abs($transaction['net_amount'])); ?>
|
|
</td>
|
|
<td><strong><?php echo wpdd_format_price($transaction['running_balance']); ?></strong></td>
|
|
<td><?php echo ucfirst($transaction['status']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p style="margin-top: 30px; font-size: 10px; color: #666;">
|
|
<?php printf(__('Generated on %s', 'wp-digital-download'), date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); ?>
|
|
</p>
|
|
|
|
<script>
|
|
// Auto-trigger print dialog for PDF saving
|
|
window.onload = function() {
|
|
// Small delay to ensure page is fully loaded
|
|
setTimeout(function() {
|
|
window.print();
|
|
}, 500);
|
|
}
|
|
|
|
// Handle after print (close window if opened in new tab)
|
|
window.onafterprint = function() {
|
|
// Check if this window was opened by another window
|
|
if (window.opener) {
|
|
window.close();
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
exit;
|
|
}
|
|
}
|