diff --git a/inc/class-registration.php b/inc/class-registration.php index 34b4aca87..7e9796229 100644 --- a/inc/class-registration.php +++ b/inc/class-registration.php @@ -512,10 +512,6 @@ function() { ), ) ); - - if ( Pro::is_pro_active() ) { - add_action( 'wp_footer', array( $this, 'test_add_temp_save' ) ); - } } if ( ! self::$scripts_loaded['google-map'] && has_block( 'themeisle-blocks/google-map', $post ) ) { @@ -966,31 +962,6 @@ public static function sticky_style() { echo ''; } - /** - * Add inline script for temporary save mode. - * - * @static - * @access public - */ - public static function test_add_temp_save() { - - /** - * In this function we add a script that will activate the temporary save mode. - * This will be for the Pro users. This will be removed in the future and moved to the Pro plugin. - */ - - if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { - return; - } - - echo ""; - } - /** * Get the content of all active widgets. * diff --git a/inc/integrations/api/form-request-data.php b/inc/integrations/api/form-request-data.php index 8d1b6a2f3..67bfbd49f 100644 --- a/inc/integrations/api/form-request-data.php +++ b/inc/integrations/api/form-request-data.php @@ -679,6 +679,15 @@ public function mark_as_duplicate() { $this->set_saving_mode( 'duplicate' ); } + /** + * Mark as temporary data. + * + * @return void + */ + public function mark_as_temporary_data() { + $this->set_saving_mode( 'temporary' ); + } + /** * Dump the data. Can be used to reconstruct the object. * @@ -740,4 +749,14 @@ public function append_metadata( $response ) { } } } + + /** + * Check if field is present in the metadata. + * + * @param string $key The key. + * @return bool + */ + public function has_metadata( $key ) { + return isset( $this->metadata[ $key ] ); + } } diff --git a/inc/integrations/api/form-response-data.php b/inc/integrations/api/form-response-data.php index 3aefc4f46..01c30360c 100644 --- a/inc/integrations/api/form-response-data.php +++ b/inc/integrations/api/form-response-data.php @@ -59,6 +59,11 @@ class Form_Data_Response { const ERROR_PROVIDER_DUPLICATED_EMAIL = '208'; const ERROR_PROVIDER_CREDENTIAL_ERROR = '209'; const ERROR_WEBHOOK_COULD_NOT_TRIGGER = '210'; + const ERROR_RUNTIME_STRIPE_SESSION_VALIDATION = '300'; + const ERROR_STRIPE_CHECKOUT_SESSION_CREATION = '301'; + const ERROR_STRIPE_CHECKOUT_SESSION_NOT_FOUND = '302'; + const ERROR_STRIPE_PAYMENT_UNPAID = '303'; + const ERROR_STRIPE_METADATA_RECORD_NOT_FOUND = '304'; /** @@ -368,6 +373,11 @@ public static function get_error_code_message( $error_code ) { self::ERROR_FILE_MISSING_BINARY => __( 'The file data is missing.', 'otter-blocks' ), self::ERROR_WEBHOOK_COULD_NOT_TRIGGER => __( 'The webhook could not be triggered.', 'otter-blocks' ), self::ERROR_MISSING_DUMP_DATA => __( 'The form dump data is missing.', 'otter-blocks' ), + self::ERROR_STRIPE_CHECKOUT_SESSION_CREATION => __( 'The Stripe Checkout session could not be created.', 'otter-blocks' ), + self::ERROR_STRIPE_CHECKOUT_SESSION_NOT_FOUND => __( 'The Stripe Checkout session was not found.', 'otter-blocks' ), + self::ERROR_STRIPE_PAYMENT_UNPAID => __( 'The payment was not completed.', 'otter-blocks' ), + self::ERROR_STRIPE_METADATA_RECORD_NOT_FOUND => __( 'The metadata submission record was not found.', 'otter-blocks' ), + self::ERROR_RUNTIME_STRIPE_SESSION_VALIDATION => __( 'The payment has been processed. You will be contacted by the support team.', 'otter-blocks' ), ); if ( ! isset( $error_messages[ $error_code ] ) ) { diff --git a/inc/server/class-form-server.php b/inc/server/class-form-server.php index 745cb7e30..e9af9bde7 100644 --- a/inc/server/class-form-server.php +++ b/inc/server/class-form-server.php @@ -17,6 +17,7 @@ use ThemeIsle\GutenbergBlocks\Integration\Form_Utils; use ThemeIsle\GutenbergBlocks\Integration\Mailchimp_Integration; use ThemeIsle\GutenbergBlocks\Integration\Sendinblue_Integration; +use ThemeIsle\GutenbergBlocks\Plugins\Stripe_API; use ThemeIsle\GutenbergBlocks\Pro; use WP_Error; use WP_HTTP_Response; @@ -187,7 +188,7 @@ public function register_routes() { 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'confirm_form' ), 'permission_callback' => function ( $request ) { - $session = $request->get_param( 'session' ); + $session = $request->get_param( 'stripe_session_id' ); if ( apply_filters( 'otter_form_session_confirmation', $session ) ) { return __return_true(); @@ -332,15 +333,12 @@ public function frontend( $request ) { */ public function confirm_form( $request ) { - $record_id = $request->get_param( 'record_id' ); - $response = new Form_Data_Response(); + $response = new Form_Data_Response(); try { - if ( ! empty( $record_id ) ) { - $response = apply_filters( 'otter_form_record_confirm', $response, $request ); - } + $response = apply_filters( 'otter_form_record_confirm', $response, $request ); } catch ( Exception $e ) { - $response->set_code( Form_Data_Response::ERROR_RUNTIME_ERROR ); + $response->set_code( Form_Data_Response::ERROR_RUNTIME_STRIPE_SESSION_VALIDATION ); $response->add_reason( $e->getMessage() ); } finally { return $response->build_response(); @@ -1032,7 +1030,7 @@ public static function pull_fields_options_for_form( $form_data ) { foreach ( $required_fields as $required_field ) { foreach ( $global_fields_options as $field ) { if ( isset( $field['fieldOptionName'] ) && $field['fieldOptionName'] === $required_field ) { - $new_field = new Form_Field_WP_Option_Data( $field_name, $field['fieldOptionType'] ); + $new_field = new Form_Field_WP_Option_Data( $required_field, $field['fieldOptionType'] ); if ( isset( $field['options'] ) ) { $new_field->set_options( $field['options'] ); } @@ -1055,8 +1053,7 @@ public static function pull_fields_options_for_form( $form_data ) { * @return bool */ public function verify_confirmation_session( $session ) { - // TODO: Add verification for Stripe session when adding the stripe field. - return 'test_123' === $session; // Test ONLY. + return ! empty( $session ) && is_string( $session ); } /** * The instance method for the static class. diff --git a/plugins/otter-pro/inc/plugins/class-form-emails-storing.php b/plugins/otter-pro/inc/plugins/class-form-emails-storing.php index 5f1154013..617063f2c 100644 --- a/plugins/otter-pro/inc/plugins/class-form-emails-storing.php +++ b/plugins/otter-pro/inc/plugins/class-form-emails-storing.php @@ -10,6 +10,7 @@ use ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request; use ThemeIsle\GutenbergBlocks\Integration\Form_Data_Response; use ThemeIsle\GutenbergBlocks\Integration\Form_Settings_Data; +use ThemeIsle\GutenbergBlocks\Plugins\Stripe_API; use ThemeIsle\GutenbergBlocks\Server\Form_Server; use WP_Post; use WP_Query; @@ -46,7 +47,6 @@ public function init() { add_action( 'init', array( $this, 'create_form_records_type' ) ); add_action( 'admin_init', array( $this, 'set_form_records_cap' ), 10, 0 ); add_action( 'otter_form_after_submit', array( $this, 'store_form_record' ) ); - add_action( 'otter_form_after_submit', array( $this, 'test_redirect_link_with_record_id' ) ); add_action( 'admin_head', array( $this, 'add_style' ) ); @@ -288,7 +288,7 @@ public function store_form_record( $form_data ) { add_post_meta( $post_id, self::FORM_RECORD_META_KEY, $meta ); - $form_data->metadata['record_id'] = $post_id; + $form_data->metadata['otter_form_record_id'] = $post_id; return $form_data; } @@ -1120,7 +1120,31 @@ private function format_based_on_status( $content, $status ) { */ public function confirm_submission( $response, $request ) { - $record_id = $request->get_param( 'record_id' ); + $session_id = $request->get_param( 'stripe_session_id' ); + + $stripe = new Stripe_API(); + + $stripe_response = $stripe->create_request( 'get_session', $session_id ); + + if ( is_wp_error( $stripe_response ) ) { + $response->set_code( Form_Data_Response::ERROR_STRIPE_CHECKOUT_SESSION_NOT_FOUND ); + return $response; + } + + $is_paid = 'paid' === $stripe_response->payment_status; + + if ( ! $is_paid ) { + $response->set_code( Form_Data_Response::ERROR_STRIPE_PAYMENT_UNPAID ); + return $response; + } + + $record_id = $stripe_response->metadata['otter_form_record_id']; + + if ( empty( $record_id ) ) { + $response->set_code( Form_Data_Response::ERROR_STRIPE_METADATA_RECORD_NOT_FOUND ); + return $response; + } + $post_status = get_post_status( $record_id ); // If the post status is not 'draft', then the submission has already been confirmed. @@ -1129,6 +1153,7 @@ public function confirm_submission( $response, $request ) { $response->mark_as_success(); return $response; } + $meta = get_post_meta( $record_id, self::FORM_RECORD_META_KEY, true ); if ( ! isset( $meta['dump'] ) || empty( $meta['dump']['value'] ) ) { @@ -1149,15 +1174,14 @@ public function confirm_submission( $response, $request ) { ( ! class_exists( 'ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request' ) ) || ! ( $form_data instanceof \ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request ) ) { - - $response->set_code( $form_data->get_error_code() ); + $response->set_code( Form_Data_Response::ERROR_RUNTIME_STRIPE_SESSION_VALIDATION ); return $response; } do_action( 'otter_form_after_submit', $form_data ); if ( $form_data->has_error() ) { - $response->set_code( $form_data->get_error_code() ); + $response->set_code( Form_Data_Response::ERROR_RUNTIME_STRIPE_SESSION_VALIDATION ); return $response; } @@ -1174,46 +1198,6 @@ public function confirm_submission( $response, $request ) { return $response; } - /** - * Test redirect link with record id. - * - * @param Form_Data_Request|null $form_data The form data. - */ - public function test_redirect_link_with_record_id( $form_data ) { - - /** - * In this manner things like Stripe confirmation link will work. - */ - - if ( ! isset( $form_data ) ) { - return $form_data; - } - - if ( - ( ! class_exists( 'ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request' ) ) || - ! ( $form_data instanceof \ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request ) || - $form_data->has_error() || - ! $form_data->is_temporary_data() - ) { - return $form_data; - } - - /** - * If the `record_id` and `stripe_payment_intent` (to be added in future PR) are set, then we can generate the confirmation link. - */ - if ( array_key_exists( 'record_id', $form_data->metadata ) ) { - $form_data->metadata['frontend_external_confirmation_url'] = add_query_arg( - array( - 'record_id' => $form_data->metadata['record_id'], // TODO: Test ONLY. This will will be extracted from Stripe payment intent metadata. - 'session' => 'test_123', - ), - $form_data->get_payload_field( 'postUrl' ) - ); - } - - return $form_data; - } - /** * The instance method for the static class. * Defines and returns the instance of the static class. diff --git a/plugins/otter-pro/inc/plugins/class-form-pro-features.php b/plugins/otter-pro/inc/plugins/class-form-pro-features.php index 8d2ddbefe..ed833476c 100644 --- a/plugins/otter-pro/inc/plugins/class-form-pro-features.php +++ b/plugins/otter-pro/inc/plugins/class-form-pro-features.php @@ -33,10 +33,12 @@ public function init() { if ( License::has_active_license() ) { add_filter( 'otter_form_data_preparation', array( $this, 'save_files_to_uploads' ) ); add_filter( 'otter_form_data_preparation', array( $this, 'load_files_to_media_library' ) ); + add_filter( 'otter_form_data_preparation', array( $this, 'mark_request_with_stripe_as_temp' ), 0 ); + add_action( 'otter_form_after_submit', array( $this, 'clean_files_from_uploads' ) ); add_action( 'otter_form_after_submit', array( $this, 'send_autoresponder' ), 99 ); add_action( 'otter_form_after_submit', array( $this, 'trigger_webhook' ) ); - add_action( 'otter_form_after_submit', array( $this, 'create_stripe_session' ) ); + add_action( 'otter_form_after_submit', array( $this, 'create_stripe_session' ), 50 ); } } @@ -515,10 +517,40 @@ public function replace_magic_tags( $content, $form_inputs ) { return $content; } + /** + * Mark request with Stripe as temp. + * + * @param Form_Data_Request|null $form_data The form data. + */ + public function mark_request_with_stripe_as_temp( $form_data ) { + if ( ! isset( $form_data ) ) { + return $form_data; + } + + if ( + ( ! class_exists( 'ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request' ) ) || + ! ( $form_data instanceof \ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request ) || + $form_data->has_error() + ) { + return $form_data; + } + + $fields_options = $form_data->get_wp_fields_options(); + + foreach ( $fields_options as $field ) { + if ( $field->has_type() && 'stripe' === $field->get_type() ) { + $form_data->mark_as_temporary_data(); + break; + } + } + + return $form_data; + } + /** * Create a Stripe session. * - * @param Form_Data_Request $form_data The form data. + * @param Form_Data_Request|null $form_data The form data. */ public function create_stripe_session( $form_data ) { if ( ! isset( $form_data ) ) { @@ -528,11 +560,16 @@ public function create_stripe_session( $form_data ) { if ( ( ! class_exists( 'ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request' ) ) || ! ( $form_data instanceof \ThemeIsle\GutenbergBlocks\Integration\Form_Data_Request ) || - $form_data->has_error() + $form_data->has_error() || + $form_data->is_duplicate() ) { return $form_data; } + if ( ! $form_data->has_metadata( 'otter_form_record_id' ) ) { + return $form_data; + } + $has_stripe = false; $fields_options = $form_data->get_wp_fields_options(); @@ -581,11 +618,6 @@ public function create_stripe_session( $form_data ) { $payload['success_url'] = $permalink; $payload['cancel_url'] = $permalink; - $customer_email = $form_data->get_email_from_form_input(); - if ( ! empty( $customer_email ) ) { - $payload['customer_email'] = $customer_email; - } - // Prepare the line items for the Stripe session request. $line_items = array(); foreach ( $products_to_process as $product ) { @@ -604,6 +636,8 @@ public function create_stripe_session( $form_data ) { foreach ( $raw_metadata as $key => $value ) { $metadata[ mb_substr( $key, 0, 40 ) ] = mb_substr( wp_json_encode( $value ), 0, 500 ); } + $metadata['otter_form_record_id'] = $form_data->metadata['otter_form_record_id']; + $payload['metadata'] = $metadata; $stripe = new Stripe_API(); @@ -613,6 +647,12 @@ public function create_stripe_session( $form_data ) { $payload ); + if ( is_wp_error( $session ) ) { + $form_data->set_error( Form_Data_Response::ERROR_STRIPE_CHECKOUT_SESSION_CREATION ); + return $form_data; + } + + $form_data->metadata['frontend_external_confirmation_url'] = $session->url; return $form_data; } diff --git a/src/blocks/frontend/form/index.js b/src/blocks/frontend/form/index.js index a03a1390c..92074a25e 100644 --- a/src/blocks/frontend/form/index.js +++ b/src/blocks/frontend/form/index.js @@ -8,27 +8,26 @@ import { domReady } from '../../helpers/frontend-helper-functions.js'; let startTimeAntiBot = null; let METADATA_VERSION = 1; -window.saveMode = 'permanent'; +let saveMode = 'permanent'; -const hasRecordId = () => { +const hasStripeConfirmation = () => { const urlParams = new URLSearchParams( window.location.search ); - return urlParams.has( 'record_id' ); + return urlParams.has( 'stripe_session_id' ); }; const confirmRecord = async() => { // Get the record id from the URL const urlParams = new URLSearchParams( window.location.search ); - const recordId = urlParams.get( 'record_id' ); // TODO: test ONLY. This will be extracted from Stripe. - const session = urlParams.get( 'session' ); + const stripeSessionId = urlParams.get( 'stripe_session_id' ); - console.log( 'Record ID: ' + recordId ); // TODO: remove after QA. + console.log( 'Session ID: ' + stripeSessionId ); // TODO: remove after QA. const formURlEndpoint = ( window?.themeisleGutenbergForm?.root || ( window.location.origin + '/wp-json/' ) ) + 'otter/v1/form/confirm'; console.log( 'Making a request for ' + formURlEndpoint ); // TODO: remove after QA. - return await fetch( formURlEndpoint + `?record_id=${recordId}&session=${session}`, { + return await fetch( formURlEndpoint + `?stripe_session_id=${stripeSessionId}`, { method: 'GET', credentials: 'include' }); @@ -144,6 +143,7 @@ const extractFormFields = async( form ) => { metadata = { fieldOptionName: input?.dataset?.fieldOptionName }; + saveMode = 'temporary'; } else { const labels = input.querySelectorAll( '.o-form-multiple-choice-field > label' ); const valuesElem = input.querySelectorAll( '.o-form-multiple-choice-field > input' ); @@ -302,8 +302,6 @@ const getCurrentPostId = () => { const handleAfterSubmit = ( request, displayMsg, onSuccess, onFail, onCleanUp ) => { request.then( r => r.json() ).then( response => { - console.log( response ); - /** * @type {import('./types.js').IFormResponse} */ @@ -456,7 +454,7 @@ const collectAndSendInputFormData = async( form, btn, displayMsg ) => { method: 'POST', headers: { 'X-WP-Nonce': window?.themeisleGutenbergForm?.nonce, - 'O-Form-Save-Mode': window.saveMode + 'O-Form-Save-Mode': saveMode }, credentials: 'include', body: formData @@ -547,7 +545,7 @@ domReady( () => { const sendBtn = form.querySelector( 'button' ); const displayMsg = new DisplayFormMessage( form ); - if ( hasRecordId() ) { + if ( hasStripeConfirmation() ) { sendBtn.disabled = true; const btnText = sendBtn.innerHTML;