| @@ -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,9 +132,10 @@ | ||
| 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 | 141 | 'secret_key' => $secret_key, |
| @@ -357,9 +359,10 @@ | ||
| 357 | 359 | if ( ! $customer_id ) { |
| 358 | 360 | throw new \Exception( __( 'Failed to create customer for subscription.', 'sureforms' ) ); |
| 359 | 361 | } |
| 360 | 362 | |
| 361 | - $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 ); | |
| 362 | 365 | // Prepare subscription data for middleware. |
| 363 | 366 | $subscription_data = apply_filters( |
| 364 | 367 | 'srfm_create_subscription_data', |
| 365 | 368 | [ |
| @@ -480,8 +483,20 @@ | ||
| 480 | 483 | } |
| 481 | 484 | |
| 482 | 485 | $payment_response = []; |
| 483 | 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 | + | |
| 484 | 499 | // Loop through form data to find payment fields. |
| 485 | 500 | foreach ( $form_data as $field_name => $field_value ) { |
| 486 | 501 | // Check if field name contains "-lbl-" pattern. |
| 487 | 502 | if ( strpos( $field_name, '-lbl-' ) === false ) { |
| @@ -500,8 +515,10 @@ | ||
| 500 | 515 | if ( ! ( strpos( $name_parts[0], 'srfm-payment-' ) === 0 ) ) { |
| 501 | 516 | continue; |
| 502 | 517 | } |
| 503 | 518 | |
| 519 | + $payment_field_names[] = $field_name; | |
| 520 | + | |
| 504 | 521 | // Value will be in the form of the json string. |
| 505 | 522 | $payment_value = json_decode( $field_value, true ); |
| 506 | 523 | |
| 507 | 524 | if ( empty( $payment_value ) || ! is_array( $payment_value ) ) { |
| @@ -545,16 +562,28 @@ | ||
| 545 | 562 | |
| 546 | 563 | if ( ! empty( $payment_response ) && isset( $payment_response['payment_id'] ) ) { |
| 547 | 564 | // Modify the form data with the payment ID. |
| 548 | 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; | |
| 549 | 570 | } |
| 550 | 571 | } |
| 551 | 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 | + | |
| 552 | 581 | if ( ! empty( $payment_response ) && isset( $payment_response['error'] ) ) { |
| 553 | - $form_data = array_merge( $form_data, $payment_response ); | |
| 582 | + return array_merge( $form_data, $payment_response ); | |
| 554 | 583 | } |
| 555 | 584 | |
| 556 | - return $form_data; | |
| 585 | + return $this->require_verified_payments( $form_data, $verified_block_ids ); | |
| 557 | 586 | } |
| 558 | 587 | |
| 559 | 588 | /** |
| 560 | 589 | * Verify Stripe payment |
| @@ -1130,8 +1159,44 @@ | ||
| 1130 | 1159 | return true; |
| 1131 | 1160 | } |
| 1132 | 1161 | |
| 1133 | 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; | |
| 1134 | 1199 | } |
| 1135 | 1200 | |
| 1136 | 1201 | /** |
| 1137 | 1202 | * Verify payment intent status |