| @@ -34,48 +34,11 @@ | ||
| 34 | 34 | */ |
| 35 | 35 | public function __construct() { |
| 36 | 36 | add_action( 'wp_ajax_suredonation_submit_donation', [ $this, 'handle_donation_submission' ] ); |
| 37 | 37 | add_action( 'wp_ajax_nopriv_suredonation_submit_donation', [ $this, 'handle_donation_submission' ] ); |
| 38 | - | |
| 39 | - // Runtime gateway configuration, read by the form script when it initialises. | |
| 40 | - add_action( 'wp_ajax_suredonation_gateway_config', [ $this, 'get_gateway_config' ] ); | |
| 41 | - add_action( 'wp_ajax_nopriv_suredonation_gateway_config', [ $this, 'get_gateway_config' ] ); | |
| 42 | 38 | } |
| 43 | 39 | |
| 44 | 40 | /** |
| 45 | - * Serve the gateway configuration for a donation form. | |
| 46 | - * | |
| 47 | - * Public read, fetched by the form script when it initialises so the Stripe | |
| 48 | - * key, PayPal SDK URL, payment mode and currency reflect the settings as | |
| 49 | - * they are now — not as they were when a page cache stored the form. It | |
| 50 | - * goes through admin-ajax, which page caches leave alone by default and | |
| 51 | - * which keeps working on sites that restrict the REST API for visitors. | |
| 52 | - * | |
| 53 | - * @return void | |
| 54 | - * @since 1.5.1 | |
| 55 | - */ | |
| 56 | - public function get_gateway_config() { | |
| 57 | - // Throttle abuse as every other public endpoint does. The ceiling is | |
| 58 | - // far above the default because this fires once per form page view, | |
| 59 | - // not per donor action, and many visitors can legitimately share one | |
| 60 | - // address (an office or campus NAT). When it trips, the scripts fall | |
| 61 | - // back to the rendered configuration rather than failing. | |
| 62 | - if ( ! Helper::check_rate_limit( 'gateway_config', 120 ) ) { | |
| 63 | - wp_send_json_error( [ 'message' => __( 'Too many requests. Please wait a moment and try again.', 'suredonation' ) ], 429 ); | |
| 64 | - } | |
| 65 | - | |
| 66 | - // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read of non-secret data; nothing changes state, and a nonce would be cached with the page it is meant to protect. | |
| 67 | - $form_id = isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : 0; | |
| 68 | - | |
| 69 | - // Freshness is the whole point of this response. admin-ajax already | |
| 70 | - // sends these, but an edge cache with a blanket rule would not care, | |
| 71 | - // so the guarantee is made explicit rather than inherited. | |
| 72 | - nocache_headers(); | |
| 73 | - | |
| 74 | - wp_send_json_success( Payment_Helper::get_frontend_gateway_config( $form_id ) ); | |
| 75 | - } | |
| 76 | - | |
| 77 | - /** | |
| 78 | 41 | * Handle donation form submission. |
| 79 | 42 | * |
| 80 | 43 | * @return void |
| 81 | 44 | * @since 0.0.1 |
| @@ -128,23 +91,19 @@ | ||
| 128 | 91 | } |
| 129 | 92 | } |
| 130 | 93 | |
| 131 | 94 | // Get form data. |
| 132 | - $amount = isset( $_POST['amount'] ) ? floatval( $_POST['amount'] ) : 0; | |
| 133 | - $cover_fees = isset( $_POST['cover_fees'] ) && 'true' === $_POST['cover_fees']; | |
| 134 | - // The anonymous flag is display-only: the donor's real name is stored as | |
| 135 | - // usual below and only public surfaces mask it. | |
| 136 | - $donor_name = sanitize_text_field( wp_unslash( $_POST['donor_name'] ?? '' ) ); | |
| 95 | + $amount = isset( $_POST['amount'] ) ? floatval( $_POST['amount'] ) : 0; | |
| 96 | + $cover_fees = isset( $_POST['cover_fees'] ) && 'true' === $_POST['cover_fees']; | |
| 97 | + $is_anonymous = isset( $_POST['is_anonymous'] ) ? true : false; | |
| 98 | + $donor_name = $is_anonymous ? __( 'Anonymous', 'suredonation' ) : sanitize_text_field( wp_unslash( $_POST['donor_name'] ?? '' ) ); | |
| 137 | 99 | $donor_email = sanitize_email( wp_unslash( $_POST['donor_email'] ?? '' ) ); |
| 100 | + $donor_phone = sanitize_text_field( wp_unslash( $_POST['donor_phone'] ?? '' ) ); | |
| 138 | 101 | $donor_comment = sanitize_textarea_field( wp_unslash( $_POST['donor_comment'] ?? '' ) ); |
| 139 | 102 | |
| 140 | 103 | // Get form_id and block_id for amount validation. |
| 141 | - $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; | |
| 142 | - $is_anonymous = Payment_Helper::get_submitted_is_anonymous( $form_id ); | |
| 143 | - // Derive the donor phone from the validated mapped field, not a separate | |
| 144 | - // unvalidated $_POST['donor_phone'] (see Payment_Helper::get_mapped_donor_phone). | |
| 145 | - $donor_phone = Payment_Helper::get_mapped_donor_phone( $form_id ); | |
| 146 | - $block_id = isset( $_POST['block_id'] ) ? sanitize_text_field( wp_unslash( $_POST['block_id'] ) ) : ''; | |
| 104 | + $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; | |
| 105 | + $block_id = isset( $_POST['block_id'] ) ? sanitize_text_field( wp_unslash( $_POST['block_id'] ) ) : ''; | |
| 147 | 106 | |
| 148 | 107 | // Validate required fields. |
| 149 | 108 | if ( $amount <= 0 ) { |
| 150 | 109 | wp_send_json_error( __( 'Invalid donation amount', 'suredonation' ) ); |
| @@ -157,23 +116,21 @@ | ||
| 157 | 116 | |
| 158 | 117 | // Validate field values + amount against block configuration. Pass the |
| 159 | 118 | // offline gateway so the Stripe-only minimum floor is not applied here. |
| 160 | 119 | $currency = Payment_Helper::get_currency(); |
| 161 | - $validation_result = Payment_Helper::validate_submission( Payment_Helper::get_submitted_fields(), $amount, $currency, $form_id, $block_id, 'offline', 'one-time' ); | |
| 120 | + $validation_result = Payment_Helper::validate_submission( Payment_Helper::get_submitted_fields(), $amount, $currency, $form_id, $block_id, 'offline' ); | |
| 162 | 121 | if ( ! $validation_result['valid'] ) { |
| 163 | 122 | wp_send_json_error( esc_html( $validation_result['message'] ) ); |
| 164 | 123 | } |
| 165 | 124 | |
| 166 | - // Name and email are required whether or not the donation is anonymous — | |
| 167 | - // the flag only masks the name on public surfaces, so there still has to | |
| 168 | - // be a real name to mask (matches the gateway handlers, which validate | |
| 169 | - // these through validate_submission() regardless of the flag). | |
| 170 | - if ( empty( $donor_name ) ) { | |
| 171 | - wp_send_json_error( __( 'Donor name is required', 'suredonation' ) ); | |
| 125 | + if ( ! $is_anonymous ) { | |
| 126 | + if ( empty( $donor_name ) ) { | |
| 127 | + wp_send_json_error( __( 'Donor name is required', 'suredonation' ) ); | |
| 128 | + } | |
| 129 | + if ( empty( $donor_email ) || ! is_email( $donor_email ) ) { | |
| 130 | + wp_send_json_error( __( 'Valid email address is required', 'suredonation' ) ); | |
| 131 | + } | |
| 172 | 132 | } |
| 173 | - if ( empty( $donor_email ) || ! is_email( $donor_email ) ) { | |
| 174 | - wp_send_json_error( __( 'Valid email address is required', 'suredonation' ) ); | |
| 175 | - } | |
| 176 | 133 | |
| 177 | 134 | // Server-side fee calculation — ignore client-supplied base_amount to prevent manipulation. |
| 178 | 135 | $base_amount = $amount; |
| 179 | 136 | $fees_covered = 0; |
| @@ -191,11 +148,13 @@ | ||
| 191 | 148 | $fees_covered = 0; |
| 192 | 149 | } |
| 193 | 150 | } |
| 194 | 151 | |
| 195 | - // Get or create donor. The email is validated as non-empty above, so | |
| 196 | - // there is no guard here — anonymous or not, this path always has one. | |
| 197 | - $donor_id = Donors::get_or_create( $donor_email, $donor_name, $donor_phone ); | |
| 152 | + // Get or create donor. | |
| 153 | + $donor_id = 0; | |
| 154 | + if ( ! empty( $donor_email ) ) { | |
| 155 | + $donor_id = Donors::get_or_create( $donor_email, $donor_name, $donor_phone ); | |
| 156 | + } | |
| 198 | 157 | |
| 199 | 158 | // Get payment mode. |
| 200 | 159 | $payment_mode = 'live'; |
| 201 | 160 | if ( class_exists( 'SureDonation\Inc\Payments\Payment_Helper' ) ) { |
| @@ -229,11 +188,8 @@ | ||
| 229 | 188 | if ( ! $donation_id ) { |
| 230 | 189 | wp_send_json_error( __( 'Failed to create donation', 'suredonation' ) ); |
| 231 | 190 | } |
| 232 | 191 | |
| 233 | - // Persist the submitted field values for the entry record. | |
| 234 | - Donations::set_submitted_fields( $donation_id, Payment_Helper::get_submitted_field_data() ); | |
| 235 | - | |
| 236 | 192 | // Note: Donation status will be updated by payment gateway webhooks or manual confirmation. |
| 237 | 193 | |
| 238 | 194 | // This donation is created as pending/manual, so send the "processing" |
| 239 | 195 | // (donation received) email rather than the completed-confirmation |
| @@ -246,13 +202,8 @@ | ||
| 246 | 202 | 'amount' => $base_amount, |
| 247 | 203 | 'fees_covered' => $fees_covered, |
| 248 | 204 | 'currency' => Payment_Helper::get_currency(), |
| 249 | 205 | 'gateway' => 'manual', |
| 250 | - // One-time regardless of the block's configured type, and intentionally | |
| 251 | - // unguarded: this handler has no remaining caller in src/, writes a | |
| 252 | - // record rather than moving money, and gating it on payment type would | |
| 253 | - // reject manual entries on recurring forms. Whether it should still be | |
| 254 | - // registered at all is the better question, tracked separately. | |
| 255 | 206 | 'donation_type' => 'one-time', |
| 256 | 207 | ]; |
| 257 | 208 | |
| 258 | 209 | Email_Handler::send_donation_processing( $donation_id, $campaign_id, $donation_data, $form_id ); |