PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/payments/front-end.php +82 -16 2.12.0 → 2.12.8 View file →
@@ -9,8 +9,9 @@
9 9 namespace SRFM\Inc\Payments;
10 10
11 11 use SRFM\Inc\Database\Tables\Payments;
12 12 use SRFM\Inc\Field_Validation;
13 +use SRFM\Inc\Helper;
13 14 use SRFM\Inc\Payments\Stripe\Stripe_Helper;
14 15 use SRFM\Inc\Submit_Token;
15 16 use SRFM\Inc\Traits\Get_Instance;
16 17
@@ -131,24 +132,26 @@
131 132 ]
132 133 );
133 134 }
134 135
135 - $license_key = Stripe_Helper::get_license_key();
136 + // Public checkout request - never block the visitor on a SureCart license call.
137 + $license_key = Stripe_Helper::get_license_key( false );
136 138
137 139 // Create payment intent with confirm: true for immediate processing.
138 140 $payment_intent_data = [
139 - 'secret_key' => $secret_key,
140 - 'amount' => $amount,
141 - 'currency' => strtolower( $currency ),
142 - 'description' => $description,
143 - 'confirm' => false, // Will be confirmed by frontend.
144 - 'receipt_email' => $customer_email,
145 - 'license_key' => $license_key,
146 - 'automatic_payment_methods' => [
147 - 'enabled' => true,
148 - 'allow_redirects' => 'never',
149 - ],
150 - 'metadata' => [
141 + 'secret_key' => $secret_key,
142 + 'amount' => $amount,
143 + 'currency' => strtolower( $currency ),
144 + 'description' => $description,
145 + 'confirm' => false, // Will be confirmed by frontend.
146 + 'receipt_email' => $customer_email,
147 + 'license_key' => $license_key,
148 + // One-time payments use manual capture; methods that don't support it (Bacs, Link, Cash App, BNPL) make
149 + // Stripe reject the deferred Elements session in live mode, and an automatic-payment-methods intent can't
150 + // be confirmed by the card-scoped client Element. Pin to card so the client Element, this payload, and the
151 + // middleware intent all agree (Apple/Google Pay are still surfaced through 'card').
152 + 'payment_method_types' => [ 'card' ],
153 + 'metadata' => [
151 154 'source' => 'SureForms',
152 155 'block_id' => $block_id,
153 156 'original_amount' => $amount,
154 157 'receipt_email' => $customer_email,
@@ -356,9 +359,10 @@
356 359 if ( ! $customer_id ) {
357 360 throw new \Exception( __( 'Failed to create customer for subscription.', 'sureforms' ) );
358 361 }
359 362
360 - $license_key = Stripe_Helper::get_license_key();
363 + // Public checkout request - never block the visitor on a SureCart license call.
364 + $license_key = Stripe_Helper::get_license_key( false );
361 365 // Prepare subscription data for middleware.
362 366 $subscription_data = apply_filters(
363 367 'srfm_create_subscription_data',
364 368 [
@@ -479,8 +483,20 @@
479 483 }
480 484
481 485 $payment_response = [];
482 486
487 + // Block IDs that produced a verified payment on this submission.
488 + $verified_block_ids = [];
489 +
490 + // Field keys of every payment field seen in this submission, and the
491 + // subset whose payment we actually verified below. A payment field's
492 + // value is only a trustworthy payment-record id once verified here; any
493 + // field left unverified is cleared before it reaches the submission data,
494 + // so the {form-payment} smart tag can never resolve a client-supplied id
495 + // to an arbitrary payment row.
496 + $payment_field_names = [];
497 + $verified_field_names = [];
498 +
483 499 // Loop through form data to find payment fields.
484 500 foreach ( $form_data as $field_name => $field_value ) {
485 501 // Check if field name contains "-lbl-" pattern.
486 502 if ( strpos( $field_name, '-lbl-' ) === false ) {
@@ -499,8 +515,10 @@
499 515 if ( ! ( strpos( $name_parts[0], 'srfm-payment-' ) === 0 ) ) {
500 516 continue;
501 517 }
502 518
519 + $payment_field_names[] = $field_name;
520 +
503 521 // Value will be in the form of the json string.
504 522 $payment_value = json_decode( $field_value, true );
505 523
506 524 if ( empty( $payment_value ) || ! is_array( $payment_value ) ) {
@@ -544,16 +562,28 @@
544 562
545 563 if ( ! empty( $payment_response ) && isset( $payment_response['payment_id'] ) ) {
546 564 // Modify the form data with the payment ID.
547 565 $form_data[ $field_name ] = $payment_response['payment_id'];
566 +
567 + $verified_block_ids[ Helper::get_string_value( $block_id ) ] = true;
568 +
569 + $verified_field_names[ $field_name ] = true;
548 570 }
549 571 }
550 572
573 + // Deny-by-default: drop any payment field we did not verify this request,
574 + // so its raw client value cannot later be read back as a payment-record id.
575 + foreach ( $payment_field_names as $payment_field_name ) {
576 + if ( ! isset( $verified_field_names[ $payment_field_name ] ) ) {
577 + $form_data[ $payment_field_name ] = '';
578 + }
579 + }
580 +
551 581 if ( ! empty( $payment_response ) && isset( $payment_response['error'] ) ) {
552 - $form_data = array_merge( $form_data, $payment_response );
582 + return array_merge( $form_data, $payment_response );
553 583 }
554 584
555 - return $form_data;
585 + return $this->require_verified_payments( $form_data, $verified_block_ids );
556 586 }
557 587
558 588 /**
559 589 * Verify Stripe payment
@@ -1129,8 +1159,44 @@
1129 1159 return true;
1130 1160 }
1131 1161
1132 1162 return $default_value;
1163 + }
1164 +
1165 + /**
1166 + * Fail closed when a form's payment field carries no verified payment.
1167 + *
1168 + * SECURITY INVARIANT — the payment requirement must come from the stored form
1169 + * config, never from the submitted payload. Verification driven by what the client
1170 + * sent can only confirm the payments it was given; it cannot know about one that
1171 + * was never presented. Deriving the requirement from the saved form keeps a
1172 + * submission that carries no payment field from being treated as complete.
1173 + *
1174 + * @param array<mixed> $form_data Form data.
1175 + * @param array<string,true> $verified_block_ids Payment block IDs verified on this submission.
1176 + *
1177 + * @since 2.12.3
1178 + * @return array<mixed> Form data, carrying an `error` key when a payment is missing.
1179 + */
1180 + private function require_verified_payments( $form_data, $verified_block_ids ) {
1181 + // absint() to match the normalisation the submit token was verified against.
1182 + $form_id = isset( $form_data['form-id'] ) ? absint( Helper::get_string_value( $form_data['form-id'] ) ) : 0;
1183 +
1184 + if ( 0 === $form_id ) {
1185 + return $form_data;
1186 + }
1187 +
1188 + foreach ( Payment_Helper::get_required_payment_block_ids( $form_id ) as $block_id ) {
1189 + if ( isset( $verified_block_ids[ Helper::get_string_value( $block_id ) ] ) ) {
1190 + continue;
1191 + }
1192 +
1193 + $form_data['error'] = Payment_Helper::get_error_message_by_key( 'payment_required' );
1194 +
1195 + break;
1196 + }
1197 +
1198 + return $form_data;
1133 1199 }
1134 1200
1135 1201 /**
1136 1202 * Verify payment intent status