logger = Qixit_Logger::get_instance(); } /** * Ensure payment details table exists before reading/writing. */ private function ensure_payment_table_exists() { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- schema check against plugin-owned table. $exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'"); if ($exists) { return true; } $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( payment_id BIGINT NOT NULL AUTO_INCREMENT, product_id BIGINT NULL, type VARCHAR(1) NOT NULL DEFAULT 'P', qixit_PID VARCHAR(255) DEFAULT NULL, qixit_PID_guid CHAR(36) DEFAULT NULL, qixit_id VARCHAR(60) DEFAULT NULL, qixit_user_id VARCHAR(100) DEFAULT NULL, txn_id VARCHAR(100) DEFAULT NULL, total DECIMAL(15,4) NOT NULL DEFAULT 0, payment_for VARCHAR(50) NOT NULL DEFAULT 'view_post', wp_user_id BIGINT DEFAULT NULL, percent_to_author DECIMAL(15,2) DEFAULT NULL, date_purchased DATETIME NULL DEFAULT NULL, PRIMARY KEY (payment_id), KEY idx_guid (qixit_PID_guid), KEY idx_user (wp_user_id), KEY idx_qixit_user (qixit_user_id), KEY idx_txn (txn_id) ) {$charset_collate}"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- CREATE TABLE uses trusted plugin constants only. $wpdb->query($sql); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- schema check against plugin-owned table. $exists_after = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'"); if (!$exists_after) { $this->logger->log('Failed to ensure payment details table exists: ' . $wpdb->last_error, 'error'); return false; } return true; } /** * Record a payment * * @param array $data Payment data * @return int|false Payment ID or false on failure */ public function record_payment($data) { global $wpdb; if (!$this->ensure_payment_table_exists()) { return false; } $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); $defaults = array( 'product_id' => 0, 'type' => 'P', // P = Purchase, A = Advertisement 'qixit_PID' => '', 'qixit_PID_guid' => null, 'qixit_id' => '', 'qixit_user_id' => '', // Qixit username (e.g., "dave70") 'txn_id' => '', // Transaction ID from Products Portal 'total' => 0.00, 'payment_for' => 'view_post', 'wp_user_id' => get_current_user_id(), 'percent_to_author' => 0, 'date_purchased' => current_time('mysql') ); $data = wp_parse_args($data, $defaults); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- direct write to plugin-owned payments table. $result = $wpdb->insert($table_name, $data); if ($result) { $this->logger->log('Payment recorded: ID ' . $wpdb->insert_id); return $wpdb->insert_id; } $this->logger->log('Failed to record payment: ' . $wpdb->last_error, 'error'); return false; } /** * Find an existing payment row by Products Portal transaction ID. * * @param string $txn_id Transaction GUID. * @return object|null */ public function get_payment_by_txn_id($txn_id) { global $wpdb; $txn_id = sanitize_text_field((string) $txn_id); if ($txn_id === '') { return null; } if (!$this->ensure_payment_table_exists()) { return null; } $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$table_name} WHERE txn_id = %s LIMIT 1", $txn_id) ); } /** * Get payment by ID * * @param int $payment_id Payment ID * @return object|null Payment object or null */ public function get_payment($payment_id) { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. return $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$table_name} WHERE payment_id = %d", $payment_id) ); } /** * Get payments for a product * * @param int $product_id Product ID * @return array Payments */ public function get_payments_by_product($product_id) { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. return $wpdb->get_results( $wpdb->prepare("SELECT * FROM {$table_name} WHERE product_id = %d ORDER BY date_purchased DESC", $product_id) ); } /** * Get payments for a user * * @param int $user_id WordPress user ID * @return array Payments */ public function get_payments_by_user($user_id) { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. return $wpdb->get_results( $wpdb->prepare("SELECT * FROM {$table_name} WHERE wp_user_id = %d ORDER BY date_purchased DESC", $user_id) ); } /** * Check if user has paid for content * * @param int $product_id Product ID * @param int $user_id WordPress user ID (optional, defaults to current user) * @return bool True if paid */ public function has_user_paid($product_id, $user_id = null) { global $wpdb; if ($user_id === null) { $user_id = get_current_user_id(); } if (!$user_id) { return false; } $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE product_id = %d AND wp_user_id = %d", $product_id, $user_id ) ); return $count > 0; } /** * Get total revenue for a product * * @param int $product_id Product ID * @return float Total revenue */ public function get_product_revenue($product_id) { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. $total = $wpdb->get_var( $wpdb->prepare("SELECT SUM(total) FROM {$table_name} WHERE product_id = %d", $product_id) ); return floatval($total); } /** * Get payment statistics * * @return array Statistics */ public function get_statistics() { global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. $total_payments = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. $total_revenue = $wpdb->get_var("SELECT SUM(total) FROM {$table_name}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name from $wpdb->prefix. $unique_buyers = $wpdb->get_var("SELECT COUNT(DISTINCT wp_user_id) FROM {$table_name}"); return array( 'total_payments' => intval($total_payments), 'total_revenue' => floatval($total_revenue), 'unique_buyers' => intval($unique_buyers) ); } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } // Initialize singleton Qixit_Payment::get_instance();