PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.1.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.1.0
1.6.1 1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
← All changes | inc/database/tables/donors.php +18 -146 1.4.0 → 1.1.0 View file →
@@ -168,9 +168,9 @@
168 168 'donor_status VARCHAR(20) NOT NULL',
169 169 'donor_data LONGTEXT',
170 170 'stripe_customer_id VARCHAR(255) DEFAULT NULL',
171 171 'import_source_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0',
172 - 'import_source VARCHAR(20) NOT NULL DEFAULT \'\'',
172 + 'import_source VARCHAR(20) NOT NULL DEFAULT ""',
173 173 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
174 174 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
175 175 'INDEX idx_email (email)',
176 176 'INDEX idx_user (user_id)',
@@ -198,12 +198,12 @@
198 198 // produces silent divergence at the data layer — a future
199 199 // `WHERE address = ''` filter would miss legacy rows that landed as
200 200 // NULL from the migration.
201 201 return [
202 - 'company VARCHAR(255) NOT NULL DEFAULT \'\' AFTER phone',
203 - 'address TEXT NOT NULL DEFAULT \'\' AFTER company',
202 + 'company VARCHAR(255) NOT NULL DEFAULT "" AFTER phone',
203 + 'address TEXT NOT NULL DEFAULT "" AFTER company',
204 204 'import_source_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER stripe_customer_id',
205 - 'import_source VARCHAR(20) NOT NULL DEFAULT \'\' AFTER import_source_id',
205 + 'import_source VARCHAR(20) NOT NULL DEFAULT "" AFTER import_source_id',
206 206 'INDEX idx_import_source (import_source_id, import_source)',
207 207 ];
208 208 }
209 209
@@ -446,13 +446,12 @@
446 446 /**
447 447 * Get or create donor by email.
448 448 *
449 449 * @param string $email Donor email.
450 - * @param string $name Donor name. Stored when creating a new donor, or backfilled onto an existing donor only when its stored name is empty; never overwrites a populated value.
451 - * @param string $phone Donor phone. Stored when creating a new donor, or backfilled onto an existing donor only when its stored phone is empty; never overwrites a populated value.
450 + * @param string $name Donor name.
451 + * @param string $phone Donor phone.
452 452 * @return int|false Donor ID or false on error.
453 453 * @since 0.0.1
454 - * @since 1.4.0 A subsequent donation no longer overwrites an existing donor's name/phone; missing values are backfilled, populated ones are left intact.
455 454 */
456 455 public static function get_or_create( $email, $name = '', $phone = '' ) {
457 456 if ( empty( $email ) ) {
458 457 return false;
@@ -460,31 +459,27 @@
460 459
461 460 $existing = self::get_by_email( $email );
462 461
463 462 if ( $existing ) {
464 - $existing_id = isset( $existing['id'] ) && is_numeric( $existing['id'] ) ? (int) $existing['id'] : 0;
463 + // Update name/phone if provided and different.
464 + $updates = [];
465 465
466 - // Backfill name/phone only when the stored value is empty — a later
467 - // donation never overwrites a populated donor name/phone. This closes
468 - // the unauthenticated-tampering vector (an attacker who knows a
469 - // donor's email cannot change that donor's existing name/phone on a
470 - // bare, unverified match) while still letting genuinely missing
471 - // details fill in from a later donation — e.g. an optional-name
472 - // gateway, or a phone field mapped after the donor's first donation.
473 - // The name/phone entered for each donation are always captured on the
474 - // donation row regardless, and admins can edit a donor directly via
475 - // the donor management endpoints.
476 - $updates = [];
477 - if ( ! empty( $name ) && '' === (string) ( $existing['name'] ?? '' ) ) {
466 + if ( ! empty( $name ) && $name !== $existing['name'] ) {
478 467 $updates['name'] = $name;
479 468 }
480 - if ( ! empty( $phone ) && '' === (string) ( $existing['phone'] ?? '' ) ) {
469 +
470 + if ( ! empty( $phone ) && $phone !== $existing['phone'] ) {
481 471 $updates['phone'] = $phone;
482 472 }
483 - if ( ! empty( $updates ) && $existing_id > 0 ) {
484 - self::update( $existing_id, $updates );
473 +
474 + if ( ! empty( $updates ) && isset( $existing['id'] ) ) {
475 + $existing_id = is_numeric( $existing['id'] ) ? (int) $existing['id'] : 0;
476 + if ( $existing_id > 0 ) {
477 + self::update( $existing_id, $updates );
478 + }
485 479 }
486 480
481 + $existing_id = isset( $existing['id'] ) && is_numeric( $existing['id'] ) ? (int) $existing['id'] : 0;
487 482 return $existing_id > 0 ? $existing_id : false;
488 483 }
489 484
490 485 // Create new donor.
@@ -803,131 +798,8 @@
803 798 return false;
804 799 }
805 800
806 801 $result = self::update( $donor_id, [ 'stripe_customer_id' => '' ] );
807 -
808 - return false !== $result;
809 - }
810 -
811 - /**
812 - * Get the Stripe customer ID for a donor on a specific connected account.
813 - *
814 - * Reads the per-account map stored in `donor_data['stripe_customers']`.
815 - * Falls back to the legacy single `stripe_customer_id` column when the
816 - * account is the site default, so pre-multi-account donors keep working.
817 - *
818 - * @param string $email Donor email.
819 - * @param string $account_id Stripe account id (`acct_…`).
820 - * @param bool $is_default Whether this is the site default account.
821 - * @return string Customer ID, or '' when none is stored.
822 - * @since 1.3.0
823 - */
824 - public static function get_stripe_customer_id_for_account( $email, $account_id, $is_default = false ) {
825 - if ( empty( $email ) || empty( $account_id ) ) {
826 - return '';
827 - }
828 -
829 - $donor = self::get_by_email( $email );
830 - if ( ! $donor ) {
831 - return '';
832 - }
833 -
834 - $donor_data = isset( $donor['donor_data'] ) && is_array( $donor['donor_data'] ) ? $donor['donor_data'] : [];
835 - $map = isset( $donor_data['stripe_customers'] ) && is_array( $donor_data['stripe_customers'] ) ? $donor_data['stripe_customers'] : [];
836 -
837 - if ( isset( $map[ $account_id ] ) && is_string( $map[ $account_id ] ) && '' !== $map[ $account_id ] ) {
838 - return $map[ $account_id ];
839 - }
840 -
841 - // Legacy fallback: the single column holds the default account's customer.
842 - if ( $is_default && ! empty( $donor['stripe_customer_id'] ) && is_string( $donor['stripe_customer_id'] ) ) {
843 - return $donor['stripe_customer_id'];
844 - }
845 -
846 - return '';
847 - }
848 -
849 - /**
850 - * Store the Stripe customer ID for a donor on a specific connected account.
851 - *
852 - * Writes the per-account map in `donor_data['stripe_customers']` and mirrors
853 - * the default account's customer into the legacy `stripe_customer_id` column
854 - * so back-compat readers keep working.
855 - *
856 - * @param string $email Donor email.
857 - * @param string $account_id Stripe account id (`acct_…`).
858 - * @param string $customer_id Stripe customer ID.
859 - * @param bool $is_default Whether this is the site default account.
860 - * @return bool True on success, false on failure.
861 - * @since 1.3.0
862 - */
863 - public static function set_stripe_customer_id_for_account( $email, $account_id, $customer_id, $is_default = false ) {
864 - if ( empty( $email ) || empty( $account_id ) || empty( $customer_id ) ) {
865 - return false;
866 - }
867 -
868 - $donor = self::get_by_email( $email );
869 - if ( ! $donor || empty( $donor['id'] ) ) {
870 - return false;
871 - }
872 - $donor_id = is_numeric( $donor['id'] ) ? (int) $donor['id'] : 0;
873 - if ( $donor_id <= 0 ) {
874 - return false;
875 - }
876 -
877 - $donor_data = isset( $donor['donor_data'] ) && is_array( $donor['donor_data'] ) ? $donor['donor_data'] : [];
878 - $map = isset( $donor_data['stripe_customers'] ) && is_array( $donor_data['stripe_customers'] ) ? $donor_data['stripe_customers'] : [];
879 -
880 - $map[ $account_id ] = sanitize_text_field( $customer_id );
881 - $donor_data['stripe_customers'] = $map;
882 -
883 - $update = [ 'donor_data' => $donor_data ];
884 - if ( $is_default ) {
885 - $update['stripe_customer_id'] = sanitize_text_field( $customer_id );
886 - }
887 -
888 - $result = self::update( $donor_id, $update );
889 -
890 - return false !== $result;
891 - }
892 -
893 - /**
894 - * Clear the stored Stripe customer ID for a donor on a specific account.
895 - *
896 - * Used when a cached customer id is no longer valid on that account
897 - * (deleted in Stripe, or a test/live mismatch).
898 - *
899 - * @param string $email Donor email.
900 - * @param string $account_id Stripe account id (`acct_…`).
901 - * @param bool $is_default Whether this is the site default account.
902 - * @return bool True on success, false on failure.
903 - * @since 1.3.0
904 - */
905 - public static function clear_stripe_customer_id_for_account( $email, $account_id, $is_default = false ) {
906 - if ( empty( $email ) || empty( $account_id ) ) {
907 - return false;
908 - }
909 -
910 - $donor = self::get_by_email( $email );
911 - if ( ! $donor || empty( $donor['id'] ) ) {
912 - return false;
913 - }
914 - $donor_id = is_numeric( $donor['id'] ) ? (int) $donor['id'] : 0;
915 - if ( $donor_id <= 0 ) {
916 - return false;
917 - }
918 -
919 - $donor_data = isset( $donor['donor_data'] ) && is_array( $donor['donor_data'] ) ? $donor['donor_data'] : [];
920 - $map = isset( $donor_data['stripe_customers'] ) && is_array( $donor_data['stripe_customers'] ) ? $donor_data['stripe_customers'] : [];
921 - unset( $map[ $account_id ] );
922 - $donor_data['stripe_customers'] = $map;
923 -
924 - $update = [ 'donor_data' => $donor_data ];
925 - if ( $is_default ) {
926 - $update['stripe_customer_id'] = '';
927 - }
928 -
929 - $result = self::update( $donor_id, $update );
930 802
931 803 return false !== $result;
932 804 }
933 805