# formidable/trunk/paypal/controllers/FrmPayPalLiteAppController.php

Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes &amp; More, version trunk. 859 lines.

- Page: https://pluginprobe.com/plugins/formidable/trunk/code/paypal/controllers/FrmPayPalLiteAppController.php
- Raw: https://pluginprobe.com/plugins/formidable/trunk/raw/paypal/controllers/FrmPayPalLiteAppController.php
- Modified: 2026-08-12T14:56:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/formidable/trunk/code/paypal/controllers/FrmPayPalLiteAppController.php#L10-L20`.

```php
<?php
if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmPayPalLiteAppController {

	/**
	 * Add the gateway for compatibility with the Payments submodule.
	 * This adds the PayPal checkbox option to the list of gateways.
	 *
	 * @param array $gateways
	 *
	 * @return array
	 */
	public static function add_gateway( $gateways ) {
		$gateways['paypal'] = array(
			'label'      => 'PayPal',
			'user_label' => __( 'Payment', 'formidable' ),
			'class'      => 'PayPalLite',
			'recurring'  => true,
			'include'    => array(
				'billing_first_name',
				'billing_last_name',
				'credit_card',
				'billing_address',
			),
		);
		return $gateways;
	}

	/**
	 * Handle the request to initialize with PayPal Api
	 */
	public static function handle_oauth() {
		FrmAppHelper::permission_check( 'frm_change_settings' );

		if ( ! check_admin_referer( 'frm_ajax', 'nonce' ) ) {
			wp_send_json_error();
		}

		$redirect_url = FrmPayPalLiteConnectHelper::get_oauth_redirect_url();

		if ( false === $redirect_url ) {
			wp_send_json_error( 'Unable to connect to PayPal successfully' );
		}

		$response_data = array(
			'redirect_url' => $redirect_url,
		);
		wp_send_json_success( $response_data );
	}

	public static function handle_disconnect() {
		FrmAppHelper::permission_check( 'frm_change_settings' );

		if ( ! check_admin_referer( 'frm_ajax', 'nonce' ) ) {
			wp_send_json_error();
		}

		FrmPayPalLiteConnectHelper::handle_disconnect();
		wp_send_json_success();
	}

	/**
	 * Get the current amount for a PayPal action via AJAX.
	 * Used to update the Pay Later messaging when price fields change.
	 *
	 * @since 6.31
	 *
	 * @return void
	 */
	public static function get_amount() {
		check_ajax_referer( 'frm_paypal_ajax', 'nonce' );

		$form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );

		if ( ! $form_id ) {
			wp_send_json_error( __( 'Invalid form ID', 'formidable' ) );
		}

		$actions = FrmPayPalLiteActionsController::get_actions_before_submit( $form_id );

		if ( ! $actions ) {
			wp_send_json_error( __( 'No PayPal actions found for this form', 'formidable' ) );
		}

		$action = reset( $actions );
		$amount = self::get_amount_value_for_verification( $action );

		wp_send_json_success( array( 'amount' => $amount ) );
	}

	/**
	 * Extract pricing data from posted form values.
	 *
	 * @since 6.31
	 *
	 * @param int $form_id The form ID.
	 *
	 * @return array Array of products with prices and quantities.
	 */
	// phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
	private static function get_pricing_data_from_posted_values( $form_id ) {
		$products = array();
		$fields   = FrmField::get_all_for_form( $form_id );

		if ( ! $fields ) {
			return $products;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$posted_data = $_POST['item_meta'] ?? array();

		foreach ( $fields as $field ) {
			if ( ! in_array( $field->type, array( 'product', 'quantity', 'total' ), true ) ) {
				continue;
			}

			$field_id = $field->id;
			$value    = $posted_data[ $field_id ] ?? '';

			if ( ! $value ) {
				continue;
			}

			if ( 'product' === $field->type ) {
				$product_field = FrmFieldFactory::get_field_object( $field );

				if ( method_exists( $product_field, 'get_posted_price' ) ) {
					$price = $product_field->get_posted_price( $value );

					if ( $price ) {
						$products[] = array(
							'name'     => $field->name,
							'price'    => is_array( $price ) ? array_sum( $price ) : $price,
							'quantity' => 1,
							'type'     => 'product',
							'field_id' => $field_id,
						);
					}
				}
			} elseif ( 'quantity' === $field->type ) {
				$quantity = is_numeric( $value ) ? (int) $value : 1;
				// Quantity fields are linked to product fields via product_field setting
				$product_field_ids = FrmField::get_option( $field, 'product_field' );

				if ( $product_field_ids ) {
					// This quantity will be associated with its product field
					// We'll handle the association in the product processing
					$products[] = array(
						'name'                             => $field->name,
						'price'                            => 0,
						// Quantity fields don't have price
												'quantity' => $quantity,
						'type'                             => 'quantity',
						'product_field_ids'                => (array) $product_field_ids,
					);
				}
			}//end if
		}//end foreach

		// Associate quantity fields with their products
		$final_products     = array();
		$product_quantities = array();

		foreach ( $products as $item ) {
			if ( 'quantity' !== $item['type'] ) {
				continue;
			}

			foreach ( $item['product_field_ids'] as $product_field_id ) {
				$product_quantities[ $product_field_id ] = $item['quantity'];
			}
		}

		foreach ( $products as $item ) {
			if ( 'product' !== $item['type'] ) {
				continue;
			}

			$quantity         = $product_quantities[ $item['field_id'] ] ?? 1;
			$final_products[] = array(
				'name'     => $item['name'],
				'price'    => $item['price'],
				'quantity' => $quantity,
			);
		}

		return $final_products;
	}

	/**
	 * Create a PayPal order via AJAX.
	 */
	public static function create_order() {
		check_ajax_referer( 'frm_paypal_ajax', 'nonce' );

		// Check if PayPal is connected before attempting to create an order.
		$connection_check = self::check_paypal_connection();

		if ( is_wp_error( $connection_check ) ) {
			wp_send_json_error( $connection_check->get_error_message() );
		}

		$form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );

		if ( ! $form_id ) {
			wp_send_json_error( __( 'Invalid form ID', 'formidable' ) );
		}

		$payment_source = FrmAppHelper::get_post_param( 'payment_source', '', 'sanitize_text_field' );

		if ( ! $payment_source ) {
			wp_send_json_error( __( 'No payment source provided', 'formidable' ) );
		}

		if ( ! in_array( $payment_source, self::get_valid_payment_sources(), true ) ) {
			wp_send_json_error( __( 'Invalid payment source', 'formidable' ) );
		}

		$actions = FrmPayPalLiteActionsController::get_actions_before_submit( $form_id );

		if ( ! $actions ) {
			wp_send_json_error( __( 'No PayPal actions found for this form', 'formidable' ) );
		}

		$action              = reset( $actions );
		$amount              = self::get_amount_value_for_verification( $action );
		$payer               = self::get_payer_data_from_posted_values( $action );
		$shipping            = self::get_shipping_data_from_posted_values( $action );
		$shipping_preference = self::get_shipping_preference( $action );
		$pricing_data        = self::get_pricing_data_from_posted_values( $form_id );
		$description         = self::process_shortcodes_for_action( $action->post_content['description'] ?? '', $action );

		if ( 0.0 === floatval( $amount ) ) {
			wp_send_json_error( __( 'Order amount cannot be zero.', 'formidable' ) );
		}

		// PayPal expects the amount in a format like 10.00, so format it.
		$amount   = number_format( floatval( $amount ), 2, '.', '' );
		$currency = strtoupper( $action->post_content['currency'] );

		$order_response = FrmPayPalLiteConnectHelper::create_order( $amount, $currency, $payment_source, $payer, $shipping_preference, $pricing_data, $shipping, $description );

		if ( class_exists( 'FrmLog' ) ) {
			$log = new FrmLog();
			$log->add(
				array(
					'title'   => 'PayPal Order Response',
					'content' => print_r( $order_response, true ),
				)
			);
		}

		if ( false === $order_response ) {
			$error = FrmPayPalLiteConnectHelper::get_latest_error_from_paypal_api();
			wp_send_json_error( self::format_paypal_error( $error, 'Failed to create PayPal order' ) );
		}

		if ( ! isset( $order_response->order_id ) ) {
			// Check if the response is a structured error with debug_id
			if ( isset( $order_response->message ) && isset( $order_response->debug_id ) ) {
				wp_send_json_error( self::format_paypal_error( $order_response->message . '{{debug_id:' . $order_response->debug_id . '}}', 'Failed to create PayPal order' ) );
			}

			wp_send_json_error( 'Failed to create PayPal order' );
		}

		wp_send_json_success( array( 'orderID' => $order_response->order_id ) );
	}

	/**
	 * @since 6.31
	 *
	 * @param WP_Post $action
	 *
	 * @return array
	 */
	private static function get_payer_data_from_posted_values( $action ) {
		$email_setting      = $action->post_content['email'];
		$first_name_setting = $action->post_content['billing_first_name'];
		$last_name_setting  = $action->post_content['billing_last_name'];

		// @phpstan-ignore-next-line
		$address_setting = $action->post_content['billing_address'] ?? '';

		$entry      = self::generate_false_entry();
		$first_name = $first_name_setting && isset( $entry->metas[ $first_name_setting ] ) ? $entry->metas[ $first_name_setting ] : '';
		$last_name  = $last_name_setting && isset( $entry->metas[ $last_name_setting ] ) ? $entry->metas[ $last_name_setting ] : '';
		$address    = $address_setting && isset( $entry->metas[ $address_setting ] ) ? $entry->metas[ $address_setting ] : '';

		if ( is_array( $first_name ) && isset( $first_name['first'] ) ) {
			$first_name = $first_name['first'];
		}

		if ( is_array( $last_name ) && isset( $last_name['last'] ) ) {
			$last_name = $last_name['last'];
		}

		$payer = array();

		if ( $first_name && $last_name ) {
			$payer['name'] = array(
				'given_name' => $first_name,
				'surname'    => $last_name,
			);
		}

		if ( $email_setting ) {
			$shortcode_atts         = array(
				'entry' => $entry,
				'form'  => $action->menu_order,
				'value' => $email_setting,
			);
			$payer['email_address'] = FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts );
		}

		self::maybe_add_address_data( $payer, $address, (int) $address_setting );

		return $payer;
	}

	/**
	 * Build shipping data from the action's shipping field settings.
	 *
	 * @since 6.31
	 *
	 * @param WP_Post $action
	 *
	 * @return array
	 */
	private static function get_shipping_data_from_posted_values( $action ) {
		$settings           = $action->post_content;
		$email_setting      = ! empty( $settings['shipping_email'] ) ? $settings['shipping_email'] : '';
		$first_name_setting = ! empty( $settings['shipping_first_name'] ) ? $settings['shipping_first_name'] : '';
		$last_name_setting  = ! empty( $settings['shipping_last_name'] ) ? $settings['shipping_last_name'] : '';
		$address_setting    = ! empty( $settings['shipping_address'] ) ? $settings['shipping_address'] : '';

		if ( ! $email_setting && ! $first_name_setting && ! $last_name_setting && ! $address_setting ) {
			return array();
		}

		$entry      = self::generate_false_entry();
		$first_name = $first_name_setting && isset( $entry->metas[ $first_name_setting ] ) ? $entry->metas[ $first_name_setting ] : '';
		$last_name  = $last_name_setting && isset( $entry->metas[ $last_name_setting ] ) ? $entry->metas[ $last_name_setting ] : '';

		if ( is_array( $first_name ) && isset( $first_name['first'] ) ) {
			$first_name = $first_name['first'];
		}

		if ( is_array( $last_name ) && isset( $last_name['last'] ) ) {
			$last_name = $last_name['last'];
		}

		$shipping = array();

		if ( $email_setting ) {
			$shortcode_atts = array(
				'entry' => $entry,
				'form'  => $action->menu_order,
				'value' => $email_setting,
			);
			$email          = FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts );

			if ( $email ) {
				$shipping['email_address'] = $email;
			}
		}

		if ( $first_name || $last_name ) {
			$shipping['name'] = array(
				'full_name' => trim( $first_name . ' ' . $last_name ),
			);
		}

		if ( ! $address_setting ) {
			return $shipping;
		}

		$address           = $entry->metas[ $address_setting ] ?? '';
		$formatted_address = self::format_address_for_paypal( $address, (int) $address_setting );

		if ( $formatted_address ) {
			$shipping['address'] = $formatted_address;
		}

		return $shipping;
	}

	/**
	 * Format a Formidable address field value into a PayPal address array.
	 *
	 * @since 6.31
	 *
	 * @param mixed $address          The address field value.
	 * @param int   $address_field_id The field ID.
	 *
	 * @return array|false The formatted address array, or false if invalid.
	 */
	private static function format_address_for_paypal( $address, $address_field_id ) {
		if ( ! is_array( $address ) || ! isset( $address['line1'] ) ) {
			return false;
		}

		$address_field = FrmField::getOne( $address_field_id );

		if ( ! $address_field ) {
			return false;
		}

		if ( 'us' === $address_field->field_options['address_type'] ) {
			$country_code = 'US';
		} else {
			$country_code = FrmAddressesController::get_country_code( $address['country'] );
		}

		if ( ! $address['line1'] || ! $address['city'] || ! $address['state'] || ! $address['zip'] || ! $country_code ) {
			return false;
		}

		return array(
			'address_line_1' => $address['line1'],
			'address_line_2' => $address['line2'] ?? '',
			'admin_area_2'   => $address['city'],
			'admin_area_1'   => $address['state'],
			'postal_code'    => $address['zip'],
			'country_code'   => $country_code,
		);
	}

	/**
	 * @since 6.31
	 *
	 * @param WP_Post $action
	 *
	 * @return string
	 */
	private static function get_shipping_preference( $action ) {
		$setting = ! empty( $action->post_content['shipping_preference'] ) ? $action->post_content['shipping_preference'] : 'use_paypal_account_data';

		switch ( $setting ) {
			case 'use_address_field_data':
				return 'SET_PROVIDED_ADDRESS';

			case 'no_shipping':
				return 'NO_SHIPPING';

			case 'use_paypal_account_data':
			default:
				return 'GET_FROM_FILE';
		}
	}

	/**
	 * @since 6.31
	 *
	 * @param array $payer
	 * @param array $address
	 * @param int   $address_field_id
	 *
	 * @return void
	 */
	private static function maybe_add_address_data( &$payer, $address, $address_field_id ) {
		if ( ! is_array( $address ) || ! isset( $address['line1'] ) ) {
			return;
		}

		$address_field = FrmField::getOne( $address_field_id );

		if ( ! $address_field ) {
			return;
		}

		if ( 'us' === $address_field->field_options['address_type'] ) {
			$country_code = 'US';
		} else {
			$country_code = FrmAddressesController::get_country_code( $address['country'] );
		}

		if ( ! $address['line1'] || ! $address['city'] || ! $address['state'] || ! $address['zip'] || ! $country_code ) {
			return;
		}

		$payer['address'] = array(
			'address_line_1' => $address['line1'],
			'address_line_2' => $address['line2'] ?? '',
			'admin_area_2'   => $address['city'],
			'admin_area_1'   => $address['state'],
			'postal_code'    => $address['zip'],
			'country_code'   => $country_code,
		);
	}

	/**
	 * @since 6.31
	 *
	 * @return array<string>
	 */
	private static function get_valid_payment_sources() {
		$sources = array(
			'card',
			'paypal',
			'apple_pay',
			'bancontact',
			'blik',
			'eps',
			'giropay',
			'ideal',
			'mybank',
			'p24',
			'sepa',
			'sofort',
			'trustly',
			'venmo',
			'paylater',
			'google_pay',
		);

		/**
		 * @since 6.31
		 *
		 * @param array<string> $sources
		 */
		return apply_filters( 'frm_paypal_valid_payment_sources', $sources );
	}

	/**
	 * Get the amount value for verification.
	 *
	 * @param WP_Post $action
	 *
	 * @return string
	 */
	private static function get_amount_value_for_verification( $action ) {
		$amount = $action->post_content['amount'];

		if ( ! str_contains( $amount, '[' ) ) {
			return $amount;
		}

		$form = FrmForm::getOne( $action->menu_order );

		if ( ! $form ) {
			return $amount;
		}

		// Update amount based on field shortcodes.
		$entry = self::generate_false_entry();

		return number_format( floatval( FrmPayPalLiteActionsController::prepare_amount( $amount, compact( 'form', 'entry', 'action' ) ) ) / 100, 2, '.', '' );
	}

	/**
	 * Create an entry object with posted values.
	 *
	 * @since 6.31
	 *
	 * @return stdClass
	 */
	private static function generate_false_entry() {
		$entry           = new stdClass();
		$entry->post_id  = 0;
		$entry->id       = 0;
		$entry->item_key = '';
		$entry->metas    = array();

		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		foreach ( $_POST as $k => $v ) {
			$k = sanitize_text_field( stripslashes( $k ) );
			$v = wp_unslash( $v );

			if ( $k !== 'item_meta' ) {
				FrmAppHelper::sanitize_value( 'wp_kses_post', $v );
				$entry->{$k} = $v;
				continue;
			}

			if ( ! is_array( $v ) ) {
				continue;
			}

			foreach ( $v as $f => $value ) {
				FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
				$entry->metas[ absint( $f ) ] = $value;
			}
		}

		return $entry;
	}

	/**
	 * Create a PayPal subscription object via AJAX.
	 *
	 * @return void
	 */
	public static function create_subscription() {
		check_ajax_referer( 'frm_paypal_ajax', 'nonce' );

		// Check if PayPal is connected before attempting to create a subscription.
		$connection_check = self::check_paypal_connection();

		if ( is_wp_error( $connection_check ) ) {
			wp_send_json_error( $connection_check->get_error_message() );
		}

		$form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );

		if ( ! $form_id ) {
			wp_send_json_error( __( 'Invalid form ID', 'formidable' ) );
		}

		$actions = FrmPayPalLiteActionsController::get_actions_before_submit( $form_id );

		if ( ! $actions ) {
			wp_send_json_error( __( 'No PayPal actions found for this form', 'formidable' ) );
		}

		$action = reset( $actions );
		$amount = self::get_amount_value_for_verification( $action );

		// PayPal expects the amount in a format like 10.00, so format it.
		$amount   = number_format( floatval( $amount ), 2, '.', '' );
		$currency = strtoupper( $action->post_content['currency'] );

		// Pass $product_name, $interval and $interval_count to the helper
		// As well as trial period and the maximum number of payments.
		// Also send subscriber email and description.
		$product_name   = self::process_shortcodes_for_action( $action->post_content['product_name'] ?? '', $action );
		$interval       = $action->post_content['interval'] ?? '';
		$interval_count = $action->post_content['interval_count'] ?? 1;
		$trial_period   = $action->post_content['trial_interval_count'] ?? '';
		$payment_limit  = $action->post_content['payment_limit'] ?? '';
		$product_type   = $action->post_content['product_type'] ?? 'SERVICE';
		$description    = self::process_shortcodes_for_action( $action->post_content['description'] ?? '', $action );

		if ( ! $product_name ) {
			wp_send_json_error( __( 'A product name is required for subscriptions. Please update your PayPal action settings.', 'formidable' ) );
		}

		if ( ! $interval ) {
			wp_send_json_error( __( 'A billing interval is required for subscriptions. Please update your PayPal action settings.', 'formidable' ) );
		}

		if ( ! $amount || '0.00' === $amount ) {
			wp_send_json_error( __( 'A valid amount is required for subscriptions. Zero amounts are not allowed.', 'formidable' ) );
		}

		$data = array(
			'amount'              => $amount,
			'currency'            => $currency,
			'product_name'        => $product_name,
			'product_type'        => $product_type,
			'interval'            => $interval,
			'interval_count'      => $interval_count,
			'trial_period'        => $trial_period,
			'payment_limit'       => $payment_limit,
			'payer'               => self::get_payer_data_from_posted_values( $action ),
			'shipping_preference' => self::get_shipping_preference( $action ),
			'description'         => $description,
		);

		$response = FrmPayPalLiteConnectHelper::create_subscription( $data );

		if ( false === $response ) {
			$error = FrmPayPalLiteConnectHelper::get_latest_error_from_paypal_api();
			wp_send_json_error( self::format_paypal_error( $error, 'Failed to create PayPal subscription' ) );
		}

		// Check if response is a structured error with message and debug_id (array or object)
		if ( is_object( $response ) && isset( $response->message ) && isset( $response->debug_id ) ) {
			wp_send_json_error( $response );
		}

		if ( ! isset( $response->subscription_id ) ) {
			wp_send_json_error( 'Failed to create PayPal subscription' );
		}

		wp_send_json_success( array( 'subscriptionID' => $response->subscription_id ) );
	}

	/**
	 * Handle a PayPal error reported by the frontend JavaScript.
	 *
	 * Logs the debug ID and error details, then returns a display message
	 * that includes the debug ID only when the current user has permission.
	 *
	 * @since 6.31
	 *
	 * @return void
	 */
	public static function report_error() {
		check_ajax_referer( 'frm_paypal_ajax', 'nonce' );

		$error_message = substr( FrmAppHelper::get_post_param( 'error_message', '', 'sanitize_text_field' ), 0, 500 );
		$debug_id      = substr( FrmAppHelper::get_post_param( 'debug_id', '', 'sanitize_text_field' ), 0, 50 );
		$context       = substr( FrmAppHelper::get_post_param( 'context', '', 'sanitize_text_field' ), 0, 100 );

		if ( $debug_id ) {
			self::log_paypal_debug_id( $debug_id, $error_message, $context );
		}

		$display_message = $error_message ? $error_message : __( 'Payment failed. Please try again.', 'formidable' );

		if ( $debug_id && current_user_can( 'frm_change_settings' ) ) {
			$display_message .= "\n\nDebug ID: " . $debug_id;
		}

		wp_send_json_success( array( 'message' => $display_message ) );
	}

	/**
	 * Log a PayPal debug ID to the recent debug IDs option.
	 *
	 * Stores up to 20 entries, newest first.
	 *
	 * @since 6.31
	 *
	 * @param string $debug_id      The PayPal debug ID.
	 * @param string $error_message The associated error message.
	 * @param string $context       The context where the error occurred (e.g. 'create_order', 'card_submit').
	 *
	 * @return void
	 */
	public static function log_paypal_debug_id( $debug_id, $error_message = '', $context = '' ) {
		$option_name = 'frm_paypal_debug_ids';
		$max_entries = 20;
		$entries     = get_option( $option_name, array() );

		if ( ! is_array( $entries ) ) {
			$entries = array();
		}

		if ( $error_message && preg_match( '/^[A-Z_]+$/', $error_message ) ) {
			$prefixes = array( 'REFUND_FAILED_', 'REFUND_' );
			$reason   = str_replace( $prefixes, '', $error_message );

			if ( $reason === $error_message ) {
				$error_message = ucwords( strtolower( str_replace( '_', ' ', $error_message ) ) );
			} else {
				$error_message = 'Refund Failed (' . ucwords( strtolower( str_replace( '_', ' ', $reason ) ) ) . ')';
			}
		}

		$entry = array(
			'debug_id'      => $debug_id,
			'error_message' => $error_message,
			'context'       => $context,
			'timestamp'     => gmdate( 'Y-m-d H:i:s' ),
		);

		array_unshift( $entries, $entry );
		$entries = array_slice( $entries, 0, $max_entries );

		update_option( $option_name, $entries, false );
	}

	/**
	 * Process shortcodes in an action setting value using posted form data.
	 *
	 * @since 6.31
	 *
	 * @param string  $value  The value that may contain shortcodes.
	 * @param WP_Post $action The payment action.
	 *
	 * @return string
	 */
	private static function process_shortcodes_for_action( $value, $action ) {
		if ( ! str_contains( $value, '[' ) ) {
			return $value;
		}

		$form = FrmForm::getOne( $action->menu_order );

		if ( ! $form ) {
			return $value;
		}

		$entry = self::generate_false_entry();

		return FrmTransLiteAppHelper::process_shortcodes(
			array(
				'value' => $value,
				'form'  => $form,
				'entry' => $entry,
			)
		);
	}

	/**
	 * Check if PayPal is connected before attempting to create an order or subscription.
	 *
	 * @since 6.31
	 *
	 * @return true|WP_Error True if connected, WP_Error with message if not connected.
	 */
	private static function check_paypal_connection() {
		$merchant_id = FrmPayPalLiteConnectHelper::get_merchant_id();

		if ( ! $merchant_id ) {
			$message = __( 'PayPal is not connected. Please connect your PayPal account to process payments.', 'formidable' );

			if ( current_user_can( 'frm_change_settings' ) ) {
				$message .= ' ' . __( 'You can connect PayPal in Global Settings, under the Payments section.', 'formidable' );
			}

			return new WP_Error( 'paypal_not_connected', $message );
		}

		return true;
	}

	/**
	 * Parse a PayPal API error string and conditionally include the debug ID.
	 *
	 * The PayPal API addon embeds debug IDs using a {{debug_id:...}} delimiter.
	 * This method strips that token and appends a human-readable debug ID line
	 * only for users who can edit forms.
	 *
	 * @since 6.31
	 *
	 * @param array|string|null $error    The error string from the PayPal API, possibly containing a debug ID token.
	 * @param string            $fallback The fallback message when no error is available.
	 *
	 * @return string
	 */
	private static function format_paypal_error( $error, $fallback ) {
		if ( ! $error ) {
			return $fallback;
		}

		// If error is already a structured array with message and debug_id, return it directly
		if ( is_array( $error ) && isset( $error['message'] ) && isset( $error['debug_id'] ) ) {
			return $error;
		}

		if ( ! preg_match( '/\{\{debug_id:([^}]+)\}\}/', $error, $matches ) ) {
			return $error;
		}

		$clean_message = str_replace( $matches[0], '', $error );
		$clean_message = trim( $clean_message );

		// Always return structured error with debug_id so JavaScript can extract and send it
		return array(
			'message'  => $clean_message ? $clean_message : $fallback,
			'debug_id' => $matches[1],
		);
	}

	/**
	 * @deprecated 6.32.1
	 */
	public static function create_vault_setup_token() {
		_deprecated_function( __METHOD__, '6.32.1' );
		wp_send_json_error( 'This API endpoint is no longer in use.' );
	}
}

```
