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/ajax/donation-handler.php +20 -69 1.5.1 → 1.1.0 View file →
@@ -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 );