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 +40 -93 1.6.0 → 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,26 +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'] ?? '' ) );
137 - $donor_email = sanitize_email( wp_unslash( $_POST['donor_email'] ?? '' ) );
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'] ?? '' ) );
99 + $donor_email = sanitize_email( wp_unslash( $_POST['donor_email'] ?? '' ) );
100 + $donor_phone = sanitize_text_field( wp_unslash( $_POST['donor_phone'] ?? '' ) );
101 + $donor_comment = sanitize_textarea_field( wp_unslash( $_POST['donor_comment'] ?? '' ) );
138 102
139 103 // Get form_id and block_id for amount validation.
140 - $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
141 - $is_anonymous = Payment_Helper::get_submitted_is_anonymous( $form_id );
142 - // Derive the donor phone from the validated mapped field, not a separate
143 - // unvalidated $_POST['donor_phone'] (see Payment_Helper::get_mapped_donor_phone).
144 - $donor_phone = Payment_Helper::get_mapped_donor_phone( $form_id );
145 - // Likewise derive the comment from the form's Donor Comment field rather
146 - // than an unvalidated $_POST['donor_comment'] (see
147 - // Payment_Helper::get_mapped_donor_comment).
148 - $donor_comment = Payment_Helper::get_mapped_donor_comment( $form_id );
149 - $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'] ) ) : '';
150 106
151 107 // Validate required fields.
152 108 if ( $amount <= 0 ) {
153 109 wp_send_json_error( __( 'Invalid donation amount', 'suredonation' ) );
@@ -160,23 +116,21 @@
160 116
161 117 // Validate field values + amount against block configuration. Pass the
162 118 // offline gateway so the Stripe-only minimum floor is not applied here.
163 119 $currency = Payment_Helper::get_currency();
164 - $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' );
165 121 if ( ! $validation_result['valid'] ) {
166 122 wp_send_json_error( esc_html( $validation_result['message'] ) );
167 123 }
168 124
169 - // Name and email are required whether or not the donation is anonymous —
170 - // the flag only masks the name on public surfaces, so there still has to
171 - // be a real name to mask (matches the gateway handlers, which validate
172 - // these through validate_submission() regardless of the flag).
173 - if ( empty( $donor_name ) ) {
174 - 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 + }
175 132 }
176 - if ( empty( $donor_email ) || ! is_email( $donor_email ) ) {
177 - wp_send_json_error( __( 'Valid email address is required', 'suredonation' ) );
178 - }
179 133
180 134 // Server-side fee calculation — ignore client-supplied base_amount to prevent manipulation.
181 135 $base_amount = $amount;
182 136 $fees_covered = 0;
@@ -194,11 +148,13 @@
194 148 $fees_covered = 0;
195 149 }
196 150 }
197 151
198 - // Get or create donor. The email is validated as non-empty above, so
199 - // there is no guard here — anonymous or not, this path always has one.
200 - $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 + }
201 157
202 158 // Get payment mode.
203 159 $payment_mode = 'live';
204 160 if ( class_exists( 'SureDonation\Inc\Payments\Payment_Helper' ) ) {
@@ -207,27 +163,26 @@
207 163
208 164 // Create donation in database.
209 165 $donation_id = Donations::add(
210 166 [
211 - 'campaign_id' => $campaign_id,
212 - 'donor_id' => $donor_id ? $donor_id : 0,
213 - 'amount' => number_format( $base_amount, 2, '.', '' ),
214 - 'fees_covered' => number_format( $fees_covered, 2, '.', '' ),
215 - 'currency' => Payment_Helper::get_currency(),
216 - 'gateway' => 'manual',
217 - 'payment_status' => 'pending',
218 - 'payment_mode' => $payment_mode,
219 - 'donor_name' => $donor_name,
220 - 'donor_email' => $donor_email,
221 - 'donor_phone' => $donor_phone,
222 - 'is_anonymous' => $is_anonymous ? 1 : 0,
223 - 'donation_type' => 'one-time',
224 - 'donor_comment' => $donor_comment,
225 - 'donor_comment_status' => Donations::initial_comment_status( $donor_comment ),
226 - 'form_id' => $form_id,
227 - 'ip_address' => Helper::get_client_ip(),
228 - 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
229 - 'referer_url' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
167 + 'campaign_id' => $campaign_id,
168 + 'donor_id' => $donor_id ? $donor_id : 0,
169 + 'amount' => number_format( $base_amount, 2, '.', '' ),
170 + 'fees_covered' => number_format( $fees_covered, 2, '.', '' ),
171 + 'currency' => Payment_Helper::get_currency(),
172 + 'gateway' => 'manual',
173 + 'payment_status' => 'pending',
174 + 'payment_mode' => $payment_mode,
175 + 'donor_name' => $donor_name,
176 + 'donor_email' => $donor_email,
177 + 'donor_phone' => $donor_phone,
178 + 'is_anonymous' => $is_anonymous ? 1 : 0,
179 + 'donation_type' => 'one-time',
180 + 'donor_comment' => $donor_comment,
181 + 'form_id' => $form_id,
182 + 'ip_address' => Helper::get_client_ip(),
183 + 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
184 + 'referer_url' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
230 185 ]
231 186 );
232 187
233 188 if ( ! $donation_id ) {
@@ -233,11 +188,8 @@
233 188 if ( ! $donation_id ) {
234 189 wp_send_json_error( __( 'Failed to create donation', 'suredonation' ) );
235 190 }
236 191
237 - // Persist the submitted field values for the entry record.
238 - Donations::set_submitted_fields( $donation_id, Payment_Helper::get_submitted_field_data() );
239 -
240 192 // Note: Donation status will be updated by payment gateway webhooks or manual confirmation.
241 193
242 194 // This donation is created as pending/manual, so send the "processing"
243 195 // (donation received) email rather than the completed-confirmation
@@ -250,13 +202,8 @@
250 202 'amount' => $base_amount,
251 203 'fees_covered' => $fees_covered,
252 204 'currency' => Payment_Helper::get_currency(),
253 205 'gateway' => 'manual',
254 - // One-time regardless of the block's configured type, and intentionally
255 - // unguarded: this handler has no remaining caller in src/, writes a
256 - // record rather than moving money, and gating it on payment type would
257 - // reject manual entries on recurring forms. Whether it should still be
258 - // registered at all is the better question, tracked separately.
259 206 'donation_type' => 'one-time',
260 207 ];
261 208
262 209 Email_Handler::send_donation_processing( $donation_id, $campaign_id, $donation_data, $form_id );