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 +69 -28 0.0.1 → 1.1.0 View file →
@@ -43,8 +43,13 @@
43 43 * @return void
44 44 * @since 0.0.1
45 45 */
46 46 public function handle_donation_submission() {
47 + // Throttle abuse on this public endpoint before doing any work.
48 + if ( ! Helper::check_rate_limit( 'submit_donation' ) ) {
49 + wp_send_json_error( __( 'Too many requests. Please wait a moment and try again.', 'suredonation' ), 429 );
50 + }
51 +
47 52 // First check if nonce exists before accessing any other POST data.
48 53 if ( ! isset( $_POST['suredonation_nonce'] ) ) {
49 54 wp_send_json_error( __( 'Security check failed', 'suredonation' ) );
50 55 }
@@ -53,17 +58,27 @@
53 58 $nonce = sanitize_text_field( wp_unslash( $_POST['suredonation_nonce'] ) );
54 59
55 60 // Now get values needed to determine nonce action.
56 61 $is_standalone = isset( $_POST['is_standalone'] ) && '1' === $_POST['is_standalone'];
57 - $campaign_id = isset( $_POST['campaign_id'] ) ? intval( $_POST['campaign_id'] ) : 0;
62 + $campaign_id = isset( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : 0;
58 63
64 + // Standalone forms must not have a campaign — prevent bypass of campaign validation.
65 + if ( $is_standalone ) {
66 + $campaign_id = 0;
67 + }
68 +
59 69 // Verify nonce - different nonce for standalone vs campaign-linked forms.
60 - $nonce_action = $is_standalone ? 'suredonation_donation_standalone' : 'suredonation_donation_' . $campaign_id;
70 + $nonce_action = Helper::get_donation_nonce_action( $campaign_id );
61 71
62 72 if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
63 73 wp_send_json_error( __( 'Security check failed', 'suredonation' ) );
64 74 }
65 75
76 + // Reject bot submissions caught by the honeypot before processing.
77 + if ( Helper::is_honeypot_spam() ) {
78 + wp_send_json_error( __( 'Your submission was flagged as spam. Please try again.', 'suredonation' ) );
79 + }
80 +
66 81 // Validate campaign only if not standalone.
67 82 $campaign = null;
68 83 if ( ! $is_standalone ) {
69 84 if ( ! $campaign_id ) {
@@ -77,9 +92,9 @@
77 92 }
78 93
79 94 // Get form data.
80 95 $amount = isset( $_POST['amount'] ) ? floatval( $_POST['amount'] ) : 0;
81 - $cover_fees = isset( $_POST['cover_fees'] ) ? true : false;
96 + $cover_fees = isset( $_POST['cover_fees'] ) && 'true' === $_POST['cover_fees'];
82 97 $is_anonymous = isset( $_POST['is_anonymous'] ) ? true : false;
83 98 $donor_name = $is_anonymous ? __( 'Anonymous', 'suredonation' ) : sanitize_text_field( wp_unslash( $_POST['donor_name'] ?? '' ) );
84 99 $donor_email = sanitize_email( wp_unslash( $_POST['donor_email'] ?? '' ) );
85 100 $donor_phone = sanitize_text_field( wp_unslash( $_POST['donor_phone'] ?? '' ) );
@@ -84,13 +99,30 @@
84 99 $donor_email = sanitize_email( wp_unslash( $_POST['donor_email'] ?? '' ) );
85 100 $donor_phone = sanitize_text_field( wp_unslash( $_POST['donor_phone'] ?? '' ) );
86 101 $donor_comment = sanitize_textarea_field( wp_unslash( $_POST['donor_comment'] ?? '' ) );
87 102
103 + // Get form_id and block_id for amount validation.
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'] ) ) : '';
106 +
88 107 // Validate required fields.
89 108 if ( $amount <= 0 ) {
90 109 wp_send_json_error( __( 'Invalid donation amount', 'suredonation' ) );
91 110 }
92 111
112 + // Require form_id and block_id for amount validation — reject if missing to prevent bypass.
113 + if ( empty( $form_id ) || empty( $block_id ) ) {
114 + wp_send_json_error( __( 'Invalid form configuration.', 'suredonation' ) );
115 + }
116 +
117 + // Validate field values + amount against block configuration. Pass the
118 + // offline gateway so the Stripe-only minimum floor is not applied here.
119 + $currency = Payment_Helper::get_currency();
120 + $validation_result = Payment_Helper::validate_submission( Payment_Helper::get_submitted_fields(), $amount, $currency, $form_id, $block_id, 'offline' );
121 + if ( ! $validation_result['valid'] ) {
122 + wp_send_json_error( esc_html( $validation_result['message'] ) );
123 + }
124 +
93 125 if ( ! $is_anonymous ) {
94 126 if ( empty( $donor_name ) ) {
95 127 wp_send_json_error( __( 'Donor name is required', 'suredonation' ) );
96 128 }
@@ -98,18 +130,24 @@
98 130 wp_send_json_error( __( 'Valid email address is required', 'suredonation' ) );
99 131 }
100 132 }
101 133
102 - // Calculate fee and amounts.
103 - $fee_percentage = 0.029; // 2.9%
104 - $fee_fixed = 0.30;
105 - $base_amount = $amount;
106 - $fees_covered = 0;
134 + // Server-side fee calculation — ignore client-supplied base_amount to prevent manipulation.
135 + $base_amount = $amount;
136 + $fees_covered = 0;
107 137
108 - if ( $cover_fees ) {
109 - // Calculate the base amount from total (reverse calculation).
110 - $base_amount = ( $amount - $fee_fixed ) / ( 1 + $fee_percentage );
111 - $fees_covered = $amount - $base_amount;
138 + if ( $cover_fees && $base_amount > 0 ) {
139 + $fee_config = Payment_Helper::get_cover_fees_config( $form_id, 'offline' );
140 +
141 + if ( ! $fee_config['enabled'] ) {
142 + $cover_fees = false;
143 + }
144 +
145 + if ( $cover_fees ) {
146 + $fees_covered = Payment_Helper::calculate_fee( $base_amount, $fee_config['fee_percentage'], $fee_config['fee_fixed'] );
147 + } else {
148 + $fees_covered = 0;
149 + }
112 150 }
113 151
114 152 // Get or create donor.
115 153 $donor_id = 0;
@@ -139,8 +177,9 @@
139 177 'donor_phone' => $donor_phone,
140 178 'is_anonymous' => $is_anonymous ? 1 : 0,
141 179 'donation_type' => 'one-time',
142 180 'donor_comment' => $donor_comment,
181 + 'form_id' => $form_id,
143 182 'ip_address' => Helper::get_client_ip(),
144 183 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
145 184 'referer_url' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
146 185 ]
@@ -151,34 +190,36 @@
151 190 }
152 191
153 192 // Note: Donation status will be updated by payment gateway webhooks or manual confirmation.
154 193
155 - // Send donation confirmation email.
194 + // This donation is created as pending/manual, so send the "processing"
195 + // (donation received) email rather than the completed-confirmation
196 + // email. The confirmation email is reserved for when payment is
197 + // actually confirmed, matching the gateway flows.
156 198 $donation_data = [
157 - 'id' => $donation_id,
158 - 'donor_name' => $donor_name,
159 - 'donor_email' => $donor_email,
160 - 'amount' => $base_amount,
161 - 'fees_covered' => $fees_covered,
162 - 'currency' => Payment_Helper::get_currency(),
199 + 'id' => $donation_id,
200 + 'donor_name' => $donor_name,
201 + 'donor_email' => $donor_email,
202 + 'amount' => $base_amount,
203 + 'fees_covered' => $fees_covered,
204 + 'currency' => Payment_Helper::get_currency(),
205 + 'gateway' => 'manual',
206 + 'donation_type' => 'one-time',
163 207 ];
164 208
165 - Email_Handler::send_donation_confirmation( $donation_id, $campaign_id, $donation_data );
209 + Email_Handler::send_donation_processing( $donation_id, $campaign_id, $donation_data, $form_id );
166 210
167 - // Get thank you message.
168 - $thank_you_message = '';
169 - if ( ! $is_standalone && $campaign_id ) {
170 - $thank_you_message = Helper::get_campaign_meta_value( $campaign_id, 'thank_you_message', '' );
211 + // Build the confirmation/thank-you HTML from the form's confirmation message.
212 + $confirmation_html = Helper::render_confirmation_message( $donation_id );
213 + if ( '' === $confirmation_html ) {
214 + $confirmation_html = esc_html__( 'Your generous contribution will make a real difference. A confirmation email has been sent to you.', 'suredonation' );
171 215 }
172 - if ( empty( $thank_you_message ) ) {
173 - $thank_you_message = esc_html__( 'Your generous contribution will make a real difference. A confirmation email has been sent to you.', 'suredonation' );
174 - }
175 216
176 217 // Send success response.
177 218 wp_send_json_success(
178 219 [
179 220 'donation_id' => $donation_id,
180 - 'message' => wp_kses_post( Helper::get_string_value( $thank_you_message ) ),
221 + 'message' => $confirmation_html,
181 222 ]
182 223 );
183 224 }
184 225 }