| Server IP : 146.59.209.152 / Your IP : 216.73.216.152 Web Server : Apache System : Linux webm009.cluster131.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64 User : monpetu ( 144298) PHP Version : 7.4.33 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/monpetu/www/ipprint/wp-content/plugins/sureforms/inc/database/tables/ |
Upload File : |
<?php
/**
* SureForms Database Payment Table Class.
*
* @link https://sureforms.com
* @since 2.0.0
* @package SureForms
* @author SureForms <https://sureforms.com/>
*/
namespace SRFM\Inc\Database\Tables;
use SRFM\Inc\Database\Base;
use SRFM\Inc\Helper;
use SRFM\Inc\Traits\Get_Instance;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
/**
* SureForms Database Payment Table Class.
*
* @since 2.0.0
*/
class Payments extends Base {
use Get_Instance;
/**
* Allowed SQL comparison operators for where conditions.
*
* @since 2.5.2
*/
private const ALLOWED_OPERATORS = [ '=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN', 'LIKE', 'NOT LIKE' ];
/**
* Allowed column names for where conditions.
*
* @since 2.5.2
*/
private const ALLOWED_COLUMNS = [
'id',
'form_id',
'block_id',
'status',
'total_amount',
'refunded_amount',
'currency',
'entry_id',
'gateway',
'type',
'mode',
'transaction_id',
'customer_id',
'subscription_id',
'subscription_status',
'parent_subscription_id',
'payment_data',
'extra',
'log',
'created_at',
'updated_at',
'srfm_txn_id',
'customer_email',
'customer_name',
];
/**
* {@inheritDoc}
*
* @var string
*/
protected $table_suffix = 'payments';
/**
* {@inheritDoc}
*
* @var int
*/
protected $table_version = 1;
/**
* Valid payment statuses (Stripe-specific).
*
* @var array<string>
* @since 2.0.0
*/
private static $valid_statuses = [
'pending',
'succeeded',
'failed',
'canceled',
'requires_action',
'requires_payment_method',
'processing',
'refunded',
'partially_refunded',
];
/**
* Valid currencies (ISO 4217).
*
* @var array<string>
* @since 2.0.0
*/
private static $valid_currencies = [
'USD',
'EUR',
'GBP',
'JPY',
'CAD',
'AUD',
'CHF',
'CNY',
'SEK',
'NZD',
'MXN',
'SGD',
'HKD',
'NOK',
'PLN',
'TRY',
'RUB',
'INR',
'BRL',
'ZAR',
'KRW',
];
/**
* Valid payment gateways.
*
* @var array<string>
* @since 2.0.0
*/
private static $valid_gateways = [
'stripe',
];
/**
* Valid payment modes.
*
* @var array<string>
* @since 2.0.0
*/
private static $valid_modes = [
'test',
'live',
];
/**
* Valid subscription statuses (Stripe-specific).
*
* @var array<string>
* @since 2.0.0
*/
private static $valid_subscription_statuses = [
'active',
'canceled',
'past_due',
'unpaid',
'trialing',
'incomplete',
'incomplete_expired',
'paused',
];
/**
* {@inheritDoc}
*/
public function get_schema() {
return [
// Payment ID.
'id' => [
'type' => 'number',
],
// Form ID.
'form_id' => [
'type' => 'number',
],
'block_id' => [
'type' => 'string',
'default' => '',
],
// Payment status (Stripe).
'status' => [
'type' => 'string',
'default' => 'pending',
],
// Total amount after discount.
'total_amount' => [
'type' => 'string',
'default' => '0.00000000',
],
// Total refunded amount.
'refunded_amount' => [
'type' => 'string',
'default' => '0.00000000',
],
// Currency code.
'currency' => [
'type' => 'string',
'default' => '',
],
// Entry ID.
'entry_id' => [
'type' => 'number',
'default' => 0,
],
// Payment gateway.
'gateway' => [
'type' => 'string',
'default' => '',
],
// Payment type.
'type' => [
'type' => 'string',
'default' => '',
],
// Payment mode (test/live).
'mode' => [
'type' => 'string',
'default' => '',
],
// Transaction ID from gateway.
'transaction_id' => [
'type' => 'string',
'default' => '',
],
// Customer ID from gateway.
'customer_id' => [
'type' => 'string',
'default' => '',
],
// Subscription ID (if recurring).
'subscription_id' => [
'type' => 'string',
'default' => '',
],
// Subscription status.
'subscription_status' => [
'type' => 'string',
'default' => '',
],
// Parent subscription payment ID (for renewal payments).
'parent_subscription_id' => [
'type' => 'number',
'default' => 0,
],
// Payment data.
'payment_data' => [
'type' => 'array',
'default' => [],
],
// Extra data (JSON).
'extra' => [
'type' => 'array',
'default' => [],
],
// Payment log.
'log' => [
'type' => 'array',
'default' => [],
],
// Created date.
'created_at' => [
'type' => 'datetime',
],
// Updated date.
'updated_at' => [
'type' => 'datetime',
],
// Transaction ID (custom format).
'srfm_txn_id' => [
'type' => 'string',
'default' => '',
],
// Customer email.
'customer_email' => [
'type' => 'string',
'default' => '',
],
// Customer name.
'customer_name' => [
'type' => 'string',
'default' => '',
],
];
}
/**
* {@inheritDoc}
*/
public function get_columns_definition() {
return [
'id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY',
'form_id BIGINT(20) UNSIGNED',
'block_id VARCHAR(255) NOT NULL',
'status VARCHAR(50) NOT NULL',
'total_amount DECIMAL(26,8) NOT NULL',
'refunded_amount DECIMAL(26,8) NOT NULL',
'currency VARCHAR(10) NOT NULL',
'entry_id BIGINT(20) UNSIGNED NOT NULL',
'gateway VARCHAR(20) NOT NULL',
'type VARCHAR(30) NOT NULL',
'mode VARCHAR(20) NOT NULL',
'transaction_id VARCHAR(50) NOT NULL',
'customer_id VARCHAR(50) NOT NULL',
'subscription_id VARCHAR(50) NOT NULL',
'subscription_status VARCHAR(20) NOT NULL',
'parent_subscription_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0',
'payment_data LONGTEXT',
'extra LONGTEXT',
'log LONGTEXT',
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
'srfm_txn_id VARCHAR(100) NOT NULL',
'customer_email VARCHAR(255) NOT NULL',
'customer_name VARCHAR(255) NOT NULL',
];
}
/**
* Add a new payment record.
*
* @param array<string,mixed> $data Payment data to insert.
* @since 2.0.0
* @return int|false The payment ID on success, false on error.
*/
public static function add( $data ) {
$instance = self::get_instance();
return $instance->use_insert( $data );
}
/**
* Update a payment record.
*
* @param int $payment_id Payment ID to update.
* @param array<string,mixed> $data Data to update.
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function update( $payment_id, $data = [] ) {
if ( empty( $payment_id ) ) {
return false;
}
return self::get_instance()->use_update( $data, [ 'id' => absint( $payment_id ) ] );
}
/**
* Get extra data for a payment.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return array<string,mixed> Extra data array.
*/
public static function get_extra_data( $payment_id ) {
if ( empty( $payment_id ) ) {
return [];
}
$result = self::get_instance()->get_results(
[ 'id' => absint( $payment_id ) ],
'extra'
);
return isset( $result[0] ) && is_array( $result[0] ) ? Helper::get_array_value( $result[0]['extra'] ) : [];
}
/**
* Update specific key in extra data.
*
* @param int $payment_id Payment ID.
* @param string $key Key to update.
* @param mixed $value Value to set.
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function update_extra_key( $payment_id, $key, $value ) {
if ( empty( $payment_id ) || empty( $key ) ) {
return false;
}
// Get current extra data.
$extra_data = self::get_extra_data( $payment_id );
// Update specific key.
$extra_data[ sanitize_key( $key ) ] = $value;
// Update payment with new extra data.
return self::update( $payment_id, [ 'extra' => $extra_data ] );
}
/**
* Add multiple key-value pairs to extra data.
*
* @param int $payment_id Payment ID.
* @param array<string,mixed> $data Key-value pairs to add.
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function add_extra_data( $payment_id, $data ) {
if ( empty( $payment_id ) || empty( $data ) || ! is_array( $data ) ) {
return false;
}
// Get current extra data.
$extra_data = self::get_extra_data( $payment_id );
// Merge new data with existing data.
foreach ( $data as $key => $value ) {
$extra_data[ sanitize_key( $key ) ] = $value;
}
// Update payment with new extra data.
return self::update( $payment_id, [ 'extra' => $extra_data ] );
}
/**
* Remove specific key from extra data.
*
* @param int $payment_id Payment ID.
* @param string $key Key to remove.
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function remove_extra_key( $payment_id, $key ) {
if ( empty( $payment_id ) || empty( $key ) ) {
return false;
}
// Get current extra data.
$extra_data = self::get_extra_data( $payment_id );
// Remove specific key.
$sanitized_key = sanitize_key( $key );
if ( isset( $extra_data[ $sanitized_key ] ) ) {
unset( $extra_data[ $sanitized_key ] );
// Update payment with modified extra data.
return self::update( $payment_id, [ 'extra' => $extra_data ] );
}
return false;
}
/**
* Get specific value from extra data.
*
* @param int $payment_id Payment ID.
* @param string $key Key to get.
* @param mixed $default Default value if key not found.
* @since 2.0.0
* @return mixed Value from extra data or default.
*/
public static function get_extra_value( $payment_id, $key, $default = null ) {
if ( empty( $payment_id ) || empty( $key ) ) {
return $default;
}
$extra_data = self::get_extra_data( $payment_id );
$sanitized_key = sanitize_key( $key );
return $extra_data[ $sanitized_key ] ?? $default;
}
/**
* Get a single payment by ID.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return array|null Payment data or null if not found.
*/
public static function get( $payment_id ) {
if ( empty( $payment_id ) ) {
return null;
}
$results = self::get_instance()->get_results( [ 'id' => absint( $payment_id ) ] );
return is_array( $results ) && isset( $results[0] ) && is_array( $results[0] ) ? $results[0] : null;
}
/**
* Get all payments with optional parameters.
*
* @param array<mixed> $args Query arguments.
* @param bool $set_limit Whether to apply limit to query.
* @since 2.0.0
* @return array Array of payments.
*/
public static function get_all( $args = [], $set_limit = true ) {
$_args = wp_parse_args(
$args,
[
'where' => [],
'columns' => '*',
'limit' => 20,
'offset' => 0,
'orderby' => 'created_at',
'order' => 'DESC',
]
);
$orderby = ! empty( $_args['orderby'] ) && is_string( $_args['orderby'] ) && in_array( $_args['orderby'], self::ALLOWED_COLUMNS, true ) ? $_args['orderby'] : 'created_at';
$order = 'ASC' === strtoupper( Helper::get_string_value( $_args['order'] ) ) ? 'ASC' : 'DESC';
$extra_queries = [
sprintf( 'ORDER BY `%1$s` %2$s', $orderby, $order ),
];
if ( $set_limit ) {
$extra_queries[] = sprintf( 'LIMIT %1$d, %2$d', absint( $_args['offset'] ), absint( $_args['limit'] ) );
}
return self::get_instance()->get_results(
$_args['where'],
$_args['columns'],
$extra_queries
);
}
/**
* Get total payments count by status.
*
* @param string $status Status to filter by ('all', 'pending', 'succeeded', etc.).
* @param int $form_id Optional form ID to filter by.
* @param array<mixed> $where_conditions Optional additional where conditions.
* @since 2.0.0
* @return int Total count.
*/
public static function get_total_payments_by_status( $status = 'all', $form_id = 0, $where_conditions = [] ) {
$instance = self::get_instance();
$where = [];
// Add status condition.
if ( 'all' !== $status ) {
$where[] = [
[
'key' => 'status',
'compare' => '=',
'value' => sanitize_text_field( $status ),
],
];
}
// Add form ID condition.
if ( $form_id > 0 ) {
$where[] = [
[
'key' => 'form_id',
'compare' => '=',
'value' => absint( $form_id ),
],
];
}
// Add additional where conditions.
if ( ! empty( $where_conditions ) ) {
$where = array_merge( $where, $where_conditions );
}
return $instance->get_total_count( $where );
}
/**
* Get payments count after specific timestamp.
*
* @param int $timestamp Unix timestamp.
* @since 2.0.0
* @return int Count of payments.
*/
public static function get_payments_count_after( $timestamp ) {
$instance = self::get_instance();
$where = [
[
[
'key' => 'created_at',
'compare' => '>=',
'value' => gmdate( 'Y-m-d H:i:s', $timestamp ),
],
],
];
return $instance->get_total_count( $where );
}
/**
* Get available months for payments.
*
* @param array<mixed> $where_conditions Optional where conditions.
* @since 2.0.0
* @return array Array of month values and labels.
*/
public static function get_available_months( $where_conditions = [] ) {
$results = self::get_instance()->get_results(
$where_conditions,
'DISTINCT DATE_FORMAT(created_at, "%Y%m") as month_value, DATE_FORMAT(created_at, "%M %Y") as month_label',
[
'ORDER BY month_value DESC',
],
false
);
$months = [];
foreach ( $results as $result ) {
if ( is_array( $result ) && isset( $result['month_value'], $result['month_label'] ) ) {
$months[ $result['month_value'] ] = $result['month_label'];
}
}
return $months;
}
/**
* Get all payment IDs for a specific form.
*
* @param int $form_id Form ID.
* @since 2.0.0
* @return array Array of payment IDs.
*/
public static function get_all_payment_ids_for_form( $form_id ) {
if ( empty( $form_id ) ) {
return [];
}
$instance = self::get_instance();
return $instance->get_results(
[
[
[
'key' => 'form_id',
'compare' => '=',
'value' => absint( $form_id ),
],
],
],
'id'
);
}
/**
* Get form IDs by payment IDs.
*
* @param array<mixed> $payment_ids Array of payment IDs.
* @since 2.0.0
* @return array Array of unique form IDs.
*/
public static function get_form_ids_by_payments( $payment_ids ) {
if ( empty( $payment_ids ) || ! is_array( $payment_ids ) ) {
return [];
}
$instance = self::get_instance();
$results = $instance->get_results(
[
[
[
'key' => 'id',
'compare' => 'IN',
'value' => array_map( 'absint', $payment_ids ),
],
],
],
'DISTINCT form_id'
);
return array_unique( array_column( $results, 'form_id' ) );
}
/**
* Get all distinct form IDs that have payments.
*
* @since 2.0.0
* @return array Array of unique form IDs that have at least one payment.
*/
public static function get_all_forms_with_payments() {
$instance = self::get_instance();
// Get distinct form IDs from the payments table.
$results = $instance->get_results(
[], // Empty where clause to get all records.
'DISTINCT form_id'
);
$form_ids = array_unique( array_column( $results, 'form_id' ) );
// Filter out any null or 0 form IDs and return as integers.
return array_filter( array_map( 'absint', $form_ids ) );
}
/**
* Delete a payment.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return int|false Number of rows deleted or false on error.
*/
public static function delete( $payment_id ) {
if ( empty( $payment_id ) ) {
return false;
}
return self::get_instance()->use_delete( [ 'id' => absint( $payment_id ) ] );
}
/**
* Get payments by entry ID.
*
* @param int $entry_id Entry ID.
* @since 2.0.0
* @return array Array of payments.
*/
public static function get_by_entry_id( $entry_id ) {
if ( empty( $entry_id ) ) {
return [];
}
return self::get_all(
[
'where' => [
[
[
'key' => 'entry_id',
'compare' => '=',
'value' => absint( $entry_id ),
],
],
],
]
);
}
/**
* Get payments by transaction ID.
*
* @param string $transaction_id Transaction ID.
* @since 2.0.0
* @return array|null Payment data or null if not found.
*/
public static function get_by_transaction_id( $transaction_id ) {
if ( empty( $transaction_id ) ) {
return null;
}
$results = self::get_all(
[
'where' => [
[
[
'key' => 'transaction_id',
'compare' => '=',
'value' => sanitize_text_field( $transaction_id ),
],
],
],
'limit' => 1,
]
);
return $results[0] ?? null;
}
/**
* Validate payment status.
*
* @param string $status Status to validate.
* @since 2.0.0
* @return bool True if valid, false otherwise.
*/
public static function is_valid_status( $status ) {
return in_array( $status, self::$valid_statuses, true );
}
/**
* Validate currency.
*
* @param string $currency Currency to validate.
* @since 2.0.0
* @return bool True if valid, false otherwise.
*/
public static function is_valid_currency( $currency ) {
return in_array( strtoupper( $currency ), self::$valid_currencies, true );
}
/**
* Validate gateway.
*
* @param string $gateway Gateway to validate.
* @since 2.0.0
* @return bool True if valid, false otherwise.
*/
public static function is_valid_gateway( $gateway ) {
return in_array( $gateway, self::$valid_gateways, true );
}
/**
* Validate mode.
*
* @param string $mode Mode to validate.
* @since 2.0.0
* @return bool True if valid, false otherwise.
*/
public static function is_valid_mode( $mode ) {
return in_array( $mode, self::$valid_modes, true );
}
/**
* Validate subscription status.
*
* @param string $status Subscription status to validate.
* @since 2.0.0
* @return bool True if valid, false otherwise.
*/
public static function is_valid_subscription_status( $status ) {
return in_array( $status, self::$valid_subscription_statuses, true );
}
/**
* Get all valid subscription statuses.
*
* @since 2.0.0
* @return array<string> Array of valid subscription statuses.
*/
public static function get_valid_subscription_statuses() {
return self::$valid_subscription_statuses;
}
/**
* Get payment data for a payment.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return array<string,mixed> Payment data array.
*/
public static function get_payment_data( $payment_id ) {
if ( empty( $payment_id ) ) {
return [];
}
$result = self::get_instance()->get_results(
[ 'id' => absint( $payment_id ) ],
'payment_data'
);
return isset( $result[0] ) && is_array( $result[0] ) ? Helper::get_array_value( $result[0]['payment_data'] ) : [];
}
/**
* Add refund data to payment_data column.
*
* @param int $payment_id Payment ID.
* @param array<mixed> $refund_data Refund data to add.
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function add_refund_to_payment_data( $payment_id, $refund_data ) {
if ( empty( $payment_id ) || empty( $refund_data ) || ! is_array( $refund_data ) ) {
return false;
}
// Extract refund ID - required for using as array key.
$refund_id = $refund_data['refund_id'] ?? '';
if ( empty( $refund_id ) ) {
return false; // Must have a refund ID.
}
// Get current payment data.
$payment_data = self::get_payment_data( $payment_id );
$payment_data = is_array( $payment_data ) ? $payment_data : [];
// Initialize refunds array if it doesn't exist.
if ( ! isset( $payment_data['refunds'] ) || ! is_array( $payment_data['refunds'] ) ) {
$payment_data['refunds'] = [];
}
// Use refund ID as array key - automatically prevents duplicates!
$payment_data['refunds'][ $refund_id ] = $refund_data;
// Update payment with new payment data.
return self::update( $payment_id, [ 'payment_data' => $payment_data ] );
}
/**
* Add refund amount to the refunded_amount column.
*
* @param int $payment_id Payment ID.
* @param float $refund_amount Refund amount to add (in dollars).
* @since 2.0.0
* @return int|false Number of rows updated or false on error.
*/
public static function add_refund_amount( $payment_id, $refund_amount ) {
if ( empty( $payment_id ) || $refund_amount <= 0 ) {
return false;
}
// Get current payment data.
$payment = self::get( $payment_id );
if ( ! $payment ) {
return false;
}
// Calculate new refunded amount.
$current_refunded = floatval( $payment['refunded_amount'] ?? 0 );
$new_total_refunded = $current_refunded + floatval( $refund_amount );
// Update refunded amount.
return self::update( $payment_id, [ 'refunded_amount' => $new_total_refunded ] );
}
/**
* Get refunded amount for a payment.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return float Refunded amount in dollars.
*/
public static function get_refunded_amount( $payment_id ) {
if ( empty( $payment_id ) ) {
return 0.0;
}
$payment = self::get( $payment_id );
if ( ! $payment ) {
return 0.0;
}
return floatval( $payment['refunded_amount'] ?? 0 );
}
/**
* Get refundable amount for a payment.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return float Remaining refundable amount in dollars.
*/
public static function get_refundable_amount( $payment_id ) {
if ( empty( $payment_id ) ) {
return 0.0;
}
$payment = self::get( $payment_id );
if ( ! $payment ) {
return 0.0;
}
$total_amount = floatval( $payment['total_amount'] ?? 0 );
$refunded_amount = floatval( $payment['refunded_amount'] ?? 0 );
return max( 0, $total_amount - $refunded_amount );
}
/**
* Check if payment is fully refunded.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return bool True if fully refunded, false otherwise.
*/
public static function is_fully_refunded( $payment_id ) {
if ( empty( $payment_id ) ) {
return false;
}
$payment = self::get( $payment_id );
if ( ! $payment ) {
return false;
}
$total_amount = floatval( $payment['total_amount'] ?? 0 );
$refunded_amount = floatval( $payment['refunded_amount'] ?? 0 );
return $refunded_amount >= $total_amount && $total_amount > 0;
}
/**
* Check if payment is partially refunded.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return bool True if partially refunded, false otherwise.
*/
public static function is_partially_refunded( $payment_id ) {
if ( empty( $payment_id ) ) {
return false;
}
$refunded_amount = self::get_refunded_amount( $payment_id );
return $refunded_amount > 0 && ! self::is_fully_refunded( $payment_id );
}
/**
* Get all individual payment transactions related to a subscription.
*
* @param string $subscription_id Stripe subscription ID.
* @since 2.0.0
* @return array Array of payment records linked to the subscription.
*/
public static function get_subscription_related_payments( $subscription_id ) {
if ( empty( $subscription_id ) ) {
return [];
}
// Get all payments with the subscription_id.
// This automatically includes.
// 1. The initial subscription payment (type='subscription').
// 2. All renewal payments (type='renewal') created by webhooks.
return self::get_all(
[
'where' => [
[
[
'key' => 'subscription_id',
'compare' => '=',
'value' => sanitize_text_field( $subscription_id ),
],
],
],
'orderby' => 'created_at',
'order' => 'DESC',
],
false
);
}
/**
* Get the main subscription record by subscription ID.
*
* @param string $subscription_id Stripe subscription ID.
* @since 2.0.0
* @return array|null Subscription payment record or null if not found.
*/
public static function get_main_subscription_record( $subscription_id ) {
if ( empty( $subscription_id ) ) {
return null;
}
$results = self::get_all(
[
'where' => [
[
[
'key' => 'subscription_id',
'compare' => '=',
'value' => sanitize_text_field( $subscription_id ),
],
[
'key' => 'type',
'compare' => '=',
'value' => 'subscription',
],
],
],
'limit' => 1,
]
);
return $results[0] ?? null;
}
/**
* Get all payments for main payments table.
* Shows: ALL payment records (subscription, renewal, payment)
* No filtering applied by default - filters only come from frontend user selections
*
* @param array<mixed> $args Query arguments.
* @param bool $set_limit Whether to apply limit to query.
* @since 2.0.0
* @return array Array of payments for main table display.
*/
public static function get_all_main_payments( $args = [], $set_limit = true ) {
global $wpdb;
$_args = wp_parse_args(
$args,
[
'where' => [],
'columns' => '*',
'limit' => 20,
'offset' => 0,
'orderby' => 'created_at',
'order' => 'DESC',
]
);
$instance = self::get_instance();
$table_name = $instance->get_tablename();
// No default filtering - show ALL transactions.
// Filters are applied only from frontend user selections via 'where' conditions.
$where_clause = 'WHERE 1=1';
$params = [];
// Handle additional where conditions if provided.
if ( ! empty( $_args['where'] ) ) {
foreach ( $_args['where'] as $where_group ) {
if ( ! is_array( $where_group ) ) {
continue;
}
$where_clause .= self::build_clause_for_group( $where_group, $params );
}
}
// Order by.
$order = 'ASC' === strtoupper( $_args['order'] ) ? 'ASC' : 'DESC';
$orderby = ! empty( $_args['orderby'] ) && is_string( $_args['orderby'] ) && in_array( $_args['orderby'], self::ALLOWED_COLUMNS, true ) ? $_args['orderby'] : 'created_at';
$order_clause = "ORDER BY {$orderby} {$order}";
// Limit clause.
$limit_clause = '';
if ( $set_limit ) {
$limit_clause = $wpdb->prepare( 'LIMIT %d, %d', absint( $_args['offset'] ), absint( $_args['limit'] ) );
}
// Build final query.
$columns = '*';
if ( ! empty( $_args['columns'] ) && is_string( $_args['columns'] ) ) {
$columns = '*' === $_args['columns'] ? '*' : esc_sql( $_args['columns'] );
}
$query = "SELECT {$columns} FROM {$table_name} {$where_clause} {$order_clause} {$limit_clause}";
// Execute query with parameters.
if ( ! empty( $params ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query string is built dynamically above based on conditions.
$query = $wpdb->prepare( $query, $params );
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with dynamic preparation; table name internal, not user input; caching not applicable for dynamic queries.
$results = $wpdb->get_results( $query, ARRAY_A );
return is_array( $results ) ? $results : [];
}
/**
* Get total payments count by status for main payments table.
* Counts: ALL payment records (no default filtering)
* Filters are applied only from frontend user selections
*
* @param string $status Status to filter by ('all', 'pending', 'succeeded', etc.).
* @param int $form_id Optional form ID to filter by.
* @param array<mixed> $where_conditions Optional additional where conditions.
* @since 2.0.0
* @return int Total count.
*/
public static function get_total_main_payments_by_status( $status = 'all', $form_id = 0, $where_conditions = [] ) {
global $wpdb;
$instance = self::get_instance();
$table_name = $instance->get_tablename();
// No default filtering - count ALL transactions.
$where_clause = '1=1';
$params = [];
// Add status condition.
if ( 'all' !== $status ) {
$where_clause .= ' AND status = %s';
$params[] = sanitize_text_field( $status );
}
// Add form ID condition.
if ( $form_id > 0 ) {
$where_clause .= ' AND form_id = %d';
$params[] = absint( $form_id );
}
// Handle additional where conditions if provided.
if ( ! empty( $where_conditions ) ) {
foreach ( $where_conditions as $where_group ) {
if ( ! is_array( $where_group ) ) {
continue;
}
$where_clause .= self::build_clause_for_group( $where_group, $params );
}
}
// Build and execute query.
$query = "SELECT COUNT(*) FROM {$table_name} WHERE {$where_clause}";
if ( ! empty( $params ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query string is built dynamically above based on conditions.
$query = $wpdb->prepare( $query, $params );
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with dynamic preparation; table name internal, not user input; caching not applicable for count operations.
$result = $wpdb->get_var( $query );
return absint( $result );
}
/**
* Check if payment is a subscription record.
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return bool True if it's a subscription record, false otherwise.
*/
public static function is_subscription_record( $payment_id ) {
if ( empty( $payment_id ) ) {
return false;
}
$payment = self::get( $payment_id );
if ( ! $payment ) {
return false;
}
return 'subscription' === ( $payment['type'] ?? '' );
}
/**
* Check if payment is a subscription-related individual payment transaction.
* These are payment records that have a subscription_id (part of a subscription billing cycle).
*
* @param int $payment_id Payment ID.
* @since 2.0.0
* @return bool True if it's a subscription-related payment transaction, false otherwise.
*/
public static function is_subscription_payment_transaction( $payment_id ) {
if ( empty( $payment_id ) ) {
return false;
}
$payment = self::get( $payment_id );
if ( ! $payment ) {
return false;
}
return 'payment' === ( $payment['type'] ?? '' ) && ! empty( $payment['subscription_id'] );
}
/**
* Build the SQL fragment for a single where group, handling both flat
* AND-only groups and explicit RELATION (OR/AND) groups.
*
* Flat group example:
* [ [ 'key' => 'status', 'compare' => '=', 'value' => 'paid' ] ]
* → " AND status = %s"
*
* RELATION group example:
* [
* 'RELATION' => 'OR',
* [ 'key' => 'status', 'compare' => '=', 'value' => 'canceled' ],
* [ 'key' => 'subscription_status', 'compare' => '=', 'value' => 'canceled' ],
* ]
* → " AND (status = %s OR subscription_status = %s)"
*
* Conditions with disallowed columns or operators are silently skipped.
* Always prefixes the returned fragment with " AND " so callers can append
* directly to a `WHERE 1=1` clause.
*
* @param array<int|string,mixed> $where_group Group of conditions, optionally with 'RELATION'.
* @param array<mixed> $params Reference to running params array; appended in place.
* @since 2.9.0
* @return string SQL fragment to append, or empty string if nothing valid.
*/
private static function build_clause_for_group( array $where_group, array &$params ) {
if ( empty( $where_group ) ) {
return '';
}
$is_relation_group = ! empty( $where_group['RELATION'] ) && is_string( $where_group['RELATION'] );
$relation = $is_relation_group && 'OR' === strtoupper( $where_group['RELATION'] )
? 'OR'
: 'AND';
$sub_clauses = [];
foreach ( $where_group as $key => $condition ) {
if ( 'RELATION' === $key || ! is_array( $condition ) ) {
continue;
}
if ( ! isset( $condition['key'], $condition['compare'], $condition['value'] ) ) {
continue;
}
if ( ! in_array( $condition['key'], self::ALLOWED_COLUMNS, true ) ) {
continue;
}
$operator = strtoupper( trim( (string) $condition['compare'] ) );
if ( ! in_array( $operator, self::ALLOWED_OPERATORS, true ) ) {
continue;
}
$column = $condition['key'];
if ( in_array( $operator, [ 'IN', 'NOT IN' ], true ) && is_array( $condition['value'] ) ) {
$ids = array_map( 'absint', $condition['value'] );
if ( empty( $ids ) ) {
$ids = [ 0 ];
}
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
$sub_clauses[] = "{$column} {$operator} ({$placeholders})";
foreach ( $ids as $id ) {
$params[] = $id;
}
} else {
$sub_clauses[] = "{$column} {$operator} %s";
$params[] = $condition['value'];
}
}
if ( empty( $sub_clauses ) ) {
return '';
}
if ( $is_relation_group ) {
return ' AND (' . implode( " {$relation} ", $sub_clauses ) . ')';
}
return ' AND ' . implode( ' AND ', $sub_clauses );
}
}