| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Better_Payment\Lite\Campaign; |
| 4 | 4 | |
| 5 | +use Better_Payment\Lite\Classes\Helper; | |
| 5 | 6 | use Better_Payment\Lite\Controller; |
| 6 | 7 | |
| 7 | 8 | if ( ! defined( 'ABSPATH' ) ) { |
| 8 | 9 | exit; |
| @@ -15,8 +16,19 @@ | ||
| 15 | 16 | * donors — it must agree with the per-transaction list the Donors Wall renders |
| 16 | 17 | * (one card per approved row). Counting distinct emails made the summary read |
| 17 | 18 | * "3 Donors" above a 5-card list; a repeat donor (or a row with an empty email, |
| 18 | 19 | * which `COUNT(DISTINCT email)` drops entirely) is one transaction here. |
| 20 | + * | |
| 21 | + * Which rows count, and what they add, is Helper::approved_donation_sql() and | |
| 22 | + * Helper::COUNTABLE_AMOUNT_SQL — the one definition every aggregation shares. | |
| 23 | + * | |
| 24 | + * A confirmed donation that charged nothing — a 100%-off promotion code, a subscription trial — | |
| 25 | + * is a donor too: Handler::confirm_stripe_session() stores it `paid` with its real amount, 0, so | |
| 26 | + * it adds one to `donor_count` and nothing to `total_raised`. Nothing here filters on the amount. | |
| 27 | + * Rows confirmed before 2.4.0 stored such a session as `no_payment_required` at the form's | |
| 28 | + * amount: one that was confirmed counts as a donor and adds 0 (its stored amount was never | |
| 29 | + * charged); one that never was (Stripe reports the same status for a free checkout at creation) | |
| 30 | + * is not counted at all. See Helper::LEGACY_CONFIRMATION_MARKER. | |
| 19 | 31 | */ |
| 20 | 32 | class CampaignStats extends Controller { |
| 21 | 33 | |
| 22 | 34 | private static int $cache_ttl = 2 * MINUTE_IN_SECONDS; |
| @@ -97,20 +109,22 @@ | ||
| 97 | 109 | |
| 98 | 110 | private static function query_stats( int $campaign_id ): array { |
| 99 | 111 | global $wpdb; |
| 100 | 112 | |
| 101 | - $payment_table = $wpdb->prefix . 'better_payment'; | |
| 102 | - $approved_statuses = "'" . implode( "','", array_map( 'esc_sql', self::approved_statuses() ) ) . "'"; | |
| 113 | + $payment_table = $wpdb->prefix . 'better_payment'; | |
| 114 | + $approved = Helper::approved_donation_sql(); | |
| 115 | + $amount = Helper::COUNTABLE_AMOUNT_SQL; | |
| 103 | 116 | |
| 117 | + // Both fragments are Helper constants (no request data, no `%`). | |
| 104 | 118 | $row = $wpdb->get_row( |
| 105 | 119 | $wpdb->prepare( |
| 106 | 120 | "SELECT |
| 107 | - COALESCE(SUM(amount), 0) AS total_raised, | |
| 121 | + COALESCE(SUM({$amount}), 0) AS total_raised, | |
| 108 | 122 | COUNT(*) AS donor_count, |
| 109 | 123 | MAX(payment_date) AS last_donation_date |
| 110 | 124 | FROM `{$payment_table}` |
| 111 | 125 | WHERE campaign_id = %s |
| 112 | - AND status IN ({$approved_statuses})", | |
| 126 | + AND {$approved}", | |
| 113 | 127 | (string) $campaign_id |
| 114 | 128 | ), |
| 115 | 129 | ARRAY_A |
| 116 | 130 | ); |
| @@ -119,16 +133,6 @@ | ||
| 119 | 133 | 'total_raised' => (float) ( $row['total_raised'] ?? 0 ), |
| 120 | 134 | 'donor_count' => (int) ( $row['donor_count'] ?? 0 ), |
| 121 | 135 | 'last_donation_date' => $row['last_donation_date'] ?? null, |
| 122 | 136 | ]; |
| 123 | - } | |
| 124 | - | |
| 125 | - /** | |
| 126 | - * `success` is the status Paystack verification writes. It was missing, so Paystack | |
| 127 | - * donations never counted toward a campaign's total or donor count — even though the | |
| 128 | - * transactions screen and analytics (Admin\DB) already treat it as paid. Only a | |
| 129 | - * transaction Paystack reported as succeeded is ever stored as `success`. | |
| 130 | - */ | |
| 131 | - private static function approved_statuses(): array { | |
| 132 | - return [ 'Completed', 'paid', 'complete', 'succeeded', 'success' ]; | |
| 133 | 137 | } |
| 134 | 138 | } |