File "FrmSquareLiteActionsController.php"

Full Path: /home/adniftyx/public_html/wp-content/plugins/formidable/square/controllers/FrmSquareLiteActionsController.php
File size: 19.57 KB
MIME-type: text/x-php
Charset: utf-8

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

class FrmSquareLiteActionsController extends FrmTransLiteActionsController {

	/**
	 * @since 6.22
	 *
	 * @param string             $callback
	 * @param array|false|object $field
	 *
	 * @return string
	 */
	public static function maybe_show_card( $callback, $field = false ) {
		if ( false === $field ) {
			// Pro isn't up to date.
			return $callback;
		}

		$form_id = is_object( $field ) ? $field->form_id : $field['form_id'];
		$actions = self::get_actions_before_submit( $form_id );

		if ( ! $actions ) {
			return $callback;
		}

		$field_id = is_object( $field ) ? $field->id : $field['id'];

		foreach ( $actions as $action ) {
			if ( (int) $action->post_content['credit_card'] === (int) $field_id ) {
				return self::class . '::show_card';
			}
		}

		return $callback;
	}

	/**
	 * Override the credit card field HTML if there is a Square action.
	 *
	 * @since 6.22
	 *
	 * @param array  $field
	 * @param string $field_name
	 * @param array  $atts
	 *
	 * @return void
	 */
	public static function show_card( $field, $field_name, $atts ) {
		$actions = self::get_actions_before_submit( $field['form_id'] );

		if ( $actions ) {
			self::load_scripts( (int) $field['form_id'] );

			$html_id = $atts['html_id'];
			include FrmStrpLiteAppHelper::plugin_path() . '/views/payments/card-field.php';
			return;
		}

		// Use the Pro function when there are no Stripe actions.
		// This is required for other gateways like Authorize.Net.
		if ( is_callable( 'FrmProCreditCardsController::show_in_form' ) ) {
			FrmProCreditCardsController::show_in_form( $field, $field_name, $atts );
		}
	}

	/**
	 * Get all published payment actions with the Square gateway that have an amount set.
	 *
	 * @since 6.22
	 *
	 * @param int|string $form_id
	 *
	 * @return array
	 */
	public static function get_actions_before_submit( $form_id ) {
		$payment_actions = self::get_actions_for_form( $form_id );

		foreach ( $payment_actions as $k => $payment_action ) {
			$gateway   = $payment_action->post_content['gateway'];
			$is_square = $gateway === 'square' || ( is_array( $gateway ) && in_array( 'square', $gateway, true ) );

			if ( ! $is_square || empty( $payment_action->post_content['amount'] ) ) {
				unset( $payment_actions[ $k ] );
			}
		}

		return $payment_actions;
	}

	/**
	 * Trigger a Square payment after a form is submitted.
	 * This is called for both one time and recurring payments.
	 *
	 * @param WP_Post  $action
	 * @param stdClass $entry
	 * @param mixed    $form
	 *
	 * @return array
	 */
	public static function trigger_gateway( $action, $entry, $form ) {
		$response = array(
			'success'      => false,
			'run_triggers' => false,
			'show_errors'  => true,
		);
		$atts     = compact( 'action', 'entry', 'form' );
		$amount   = self::prepare_amount( $action->post_content['amount'], $atts );

		// phpcs:ignore Universal.Operators.StrictComparisons
		if ( ! $amount || $amount == 000 ) {
			$response['error'] = __( 'Please specify an amount for the payment', 'formidable' );
			return $response;
		}

		if ( ! self::square_is_configured() ) {
			$response['error'] = __( 'There was a problem communicating with Square. Please try again.', 'formidable' );
			return $response;
		}

		$payment_args = compact( 'form', 'entry', 'action', 'amount' );

		// Attempt to charge the customer's card.
		if ( 'recurring' === $action->post_content['type'] ) {
			$charge = self::trigger_recurring_payment( $payment_args );
		} else {
			$charge = self::trigger_one_time_payment( $payment_args );
		}

		if ( $charge === true ) {
			$response['success'] = true;
		} else {
			$response['error'] = $charge;
		}

		return $response;
	}

	/**
	 * Trigger a one time payment.
	 *
	 * @param array $atts The arguments for the payment.
	 *
	 * @return string|true string on error, true on success.
	 */
	private static function trigger_one_time_payment( $atts ) {
		if ( empty( $_POST['square-token'] ) || empty( $_POST['square-verification-token'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			return __( 'Please enter a valid credit card', 'formidable' );
		}

		$currency           = strtoupper( $atts['action']->post_content['currency'] );
		$square_token       = sanitize_text_field( $_POST['square-token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$verification_token = sanitize_text_field( $_POST['square-verification-token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$description        = FrmTransLiteAppHelper::process_shortcodes(
			array(
				'entry' => $atts['entry'],
				'form'  => $atts['entry']->form_id,
				'value' => $atts['action']->post_content['description'],
			)
		);
		$result             = FrmSquareLiteConnectHelper::create_payment( $atts['amount'], $currency, $square_token, $verification_token, $description );

		if ( false === $result ) {
			return FrmSquareLiteConnectHelper::get_latest_error_from_square_api();
		}

		$atts['status']         = $result->status === 'COMPLETED' ? 'complete' : 'failed';
		$atts['charge']         = new stdClass();
		$atts['charge']->id     = $result->id;
		$atts['charge']->amount = $atts['amount'];

		$payment_id  = self::create_new_payment( $atts );
		$frm_payment = new FrmTransLitePayment();
		$payment     = $frm_payment->get_one( $payment_id );
		$status      = $atts['status'];

		FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );

		return true;
	}

	/**
	 * Add a payment row for the payments table.
	 *
	 * @param array $atts The arguments for the payment.
	 *
	 * @return int
	 */
	private static function create_new_payment( $atts ) {
		$atts['charge'] = (object) $atts['charge'];

		$new_values = array(
			'amount'     => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $atts['charge']->amount, $atts['action'] ),
			'status'     => $atts['status'],
			'paysys'     => 'square',
			'item_id'    => $atts['entry']->id,
			'action_id'  => $atts['action']->ID,
			'receipt_id' => $atts['charge']->id,
			'sub_id'     => $atts['charge']->sub_id ?? '',
			'test'       => 'test' === FrmSquareLiteAppHelper::active_mode() ? 1 : 0,
		);

		$frm_payment = new FrmTransLitePayment();
		return $frm_payment->create( $new_values );
	}

	/**
	 * Create a new Square subscription and a subscription and payment for the payments tables.
	 *
	 * @param array $atts Includes 'customer', 'entry', 'action', 'amount'.
	 *
	 * @return bool|string True on success, error message on failure
	 */
	private static function trigger_recurring_payment( $atts ) {
		if ( empty( $_POST['square-token'] ) || empty( $_POST['square-verification-token'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			return __( 'Please enter a valid credit card', 'formidable' );
		}

		$currency           = strtoupper( $atts['action']->post_content['currency'] );
		$square_token       = sanitize_text_field( $_POST['square-token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$verification_token = sanitize_text_field( $_POST['square-verification-token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

		// We can put this all behind our API.
		// It will require that we pass the customer info and the catalog info.
		// 1. Call the API with the customer and catalog info.
		// 2. Add the database rows.

		$action          = $atts['action'];
		$billing_contact = FrmSquareLiteAppController::get_billing_contact( $action );

		$info = array(
			'payment'  => array(
				'token'             => $square_token,
				'verificationToken' => $verification_token,
			),
			'customer' => array(
				'givenName'    => $billing_contact['givenName'],
				'familyName'   => $billing_contact['familyName'],
				'emailAddress' => $billing_contact['email'],
			),
			'catalog'  => array(
				'name'      => self::prepare_subscription_description( $action->post_content['description'], $atts ),
				'trialDays' => $action->post_content['trial_interval_count'],
				'limit'     => $action->post_content['payment_limit'],
				'amount'    => $atts['amount'],
				'currency'  => $currency,
				'cadence'   => $action->post_content['repeat_cadence'] ?? 'DAILY',
			),
		);

		if ( isset( $billing_contact['addressLines'] ) ) {
			$info['customer']['address'] = array(
				'addressLine1'                 => $billing_contact['addressLines'][0],
				'addressLine2'                 => $billing_contact['addressLines'][1],
				'locality'                     => $billing_contact['city'],
				'administrativeDistrictLevel1' => $billing_contact['state'],
				'postalCode'                   => $billing_contact['postalCode'],
				'country'                      => $billing_contact['countryCode'],
			);
		}

		$response = FrmSquareLiteConnectHelper::create_subscription( $info );

		if ( false === $response ) {
			return FrmSquareLiteConnectHelper::get_latest_error_from_square_api();
		}

		if ( is_string( $response ) ) {
			return $response;
		}

		if ( ! is_object( $response ) || ! isset( $response->id ) ) {
			return __( 'There was a problem creating the subscription', 'formidable' );
		}

		// Add subscription database row.
		// We do not add a payment row at this time. This is handled with our webhook handling.
		self::create_new_subscription( $response->id, $atts );

		return true;
	}

	/**
	 * Prepare the description for a subscription.
	 *
	 * @param string $description
	 * @param array  $atts
	 *
	 * @return string
	 */
	private static function prepare_subscription_description( $description, $atts ) {
		$shortcode_atts = array(
			'entry' => $atts['entry'],
			'form'  => $atts['entry']->form_id,
			'value' => $description,
		);
		return FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts );
	}

	/**
	 * Create a new subscription and payment for the payments tables.
	 *
	 * @param string $subscription_id
	 * @param array  $atts
	 *
	 * @return int
	 */
	private static function create_new_subscription( $subscription_id, $atts ) {
		$repeat_cadence = $atts['action']->post_content['repeat_cadence'] ?? 'DAILY';
		$interval_count = self::get_interval_count_from_repeat_cadence( $repeat_cadence );
		$interval       = self::get_interval_from_repeat_cadence( $repeat_cadence );

		$new_values = array(
			'amount'         => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $atts['amount'], $atts['action'] ),
			'paysys'         => 'square',
			'item_id'        => $atts['entry']->id,
			'action_id'      => $atts['action']->ID,
			'sub_id'         => $subscription_id,
			'interval_count' => $interval_count,
			'time_interval'  => $interval,
			'status'         => 'active',
			'next_bill_date' => gmdate( 'Y-m-d' ),
			'test'           => 'test' === FrmSquareLiteAppHelper::active_mode() ? 1 : 0,
		);

		$frm_payment = new FrmTransLiteSubscription();

		return $frm_payment->create( $new_values );
	}

	/**
	 * @param string $repeat_cadence
	 *
	 * @return int
	 */
	private static function get_interval_count_from_repeat_cadence( $repeat_cadence ) {
		switch ( $repeat_cadence ) {
			case 'NINETY_DAYS':
				return 90;
			case 'SIXTY_DAYS':
				return 60;
			case 'THIRTY_DAYS':
				return 30;
			case 'EVERY_SIX_MONTHS':
				return 6;
			case 'EVERY_FOUR_MONTHS':
				return 4;
			case 'QUARTERLY':
				return 3;
			case 'EVERY_TWO_WEEKS':
			case 'EVERY_TWO_MONTHS':
			case 'EVERY_TWO_YEARS':
				return 2;
			case 'DAILY':
			case 'ANNUAL':
			case 'MONTHLY':
			case 'WEEKLY':
			default:
				return 1;
		}//end switch
	}

	/**
	 * @param string $repeat_cadence
	 *
	 * @return string
	 */
	private static function get_interval_from_repeat_cadence( $repeat_cadence ) {
		switch ( $repeat_cadence ) {
			case 'ANNUAL':
			case 'EVERY_TWO_YEARS':
				return 'year';

			case 'MONTHLY':
			case 'EVERY_TWO_MONTHS':
			case 'QUARTERLY':
			case 'EVERY_FOUR_MONTHS':
			case 'EVERY_SIX_MONTHS':
				return 'month';

			case 'WEEKLY':
			case 'EVERY_TWO_WEEKS':
				return 'week';

			case 'DAILY':
			case 'THIRTY_DAYS':
			case 'SIXTY_DAYS':
			case 'NINETY_DAYS':
			default:
				return 'day';
		}//end switch
	}

	/**
	 * Check if Square integration is enabled.
	 *
	 * @return bool true if Square is set up.
	 */
	private static function square_is_configured() {
		return (bool) FrmSquareLiteConnectHelper::get_merchant_id();
	}

	/**
	 * Replace an [email] shortcode with the current user email.
	 *
	 * @param string $email
	 *
	 * @return string
	 */
	private static function replace_email_shortcode( $email ) {
		if ( ! str_contains( $email, '[email]' ) ) {
			return $email;
		}

		global $current_user;
		return str_replace(
			'[email]',
			! empty( $current_user->user_email ) ? $current_user->user_email : '',
			$email
		);
	}

	/**
	 * Convert the amount from 10.00 to 1000.
	 *
	 * @param mixed $amount
	 * @param array $atts
	 *
	 * @return string
	 */
	public static function prepare_amount( $amount, $atts = array() ) {
		$amount   = parent::prepare_amount( $amount, $atts );
		$currency = self::get_currency_for_action( $atts );
		return number_format( $amount, $currency['decimals'], '', '' );
	}

	/**
	 * If this form submits with ajax, load the scripts on the first page.
	 *
	 * @param array $params
	 *
	 * @return void
	 */
	public static function maybe_load_scripts( $params ) {
		// phpcs:ignore Universal.Operators.StrictComparisons
		if ( $params['form_id'] == $params['posted_form_id'] ) {
			// This form has already been posted, so we aren't on the first page.
			return;
		}

		$form = FrmForm::getOne( $params['form_id'] );

		if ( ! $form ) {
			return;
		}

		$credit_card_field = FrmField::getAll(
			array(
				'fi.form_id' => $form->id,
				'type'       => 'credit_card',
			)
		);

		if ( ! $credit_card_field ) {
			return;
		}

		$payment_actions = self::get_actions_before_submit( $form->id );

		if ( ! $payment_actions ) {
			return;
		}

		$found_gateway = false;

		foreach ( $payment_actions as $action ) {
			$gateways = $action->post_content['gateway'];

			if ( in_array( 'square', (array) $gateways, true ) ) {
				$found_gateway = true;
				break;
			}
		}

		if ( ! $found_gateway ) {
			return;
		}

		self::load_scripts( (int) $form->id );
	}

	/**
	 * Load front end JavaScript for a Stripe form.
	 *
	 * @param int $form_id
	 *
	 * @return void
	 */
	public static function load_scripts( $form_id ) {
		if ( FrmAppHelper::is_admin_page( 'formidable-entries' ) ) {
			return;
		}

		if ( wp_script_is( 'formidable-square', 'enqueued' ) ) {
			return;
		}

		if ( ! $form_id || ! is_int( $form_id ) ) {
			_doing_it_wrong( __METHOD__, '$form_id parameter must be a non-zero integer', '6.22' );
			return;
		}

		$action_settings = self::prepare_settings_for_js( $form_id );
		$found_gateway   = false;

		foreach ( $action_settings as $action ) {
			$gateways = $action['gateways'];

			if ( ! $gateways || in_array( 'square', (array) $gateways, true ) ) {
				$found_gateway = true;
				break;
			}
		}

		if ( ! $found_gateway ) {
			return;
		}

		wp_register_script(
			'square',
			FrmSquareLiteAppHelper::active_mode() === 'live' ? 'https://web.squarecdn.com/v1/square.js' : 'https://sandbox.web.squarecdn.com/v1/square.js',
			array(),
			'1.0',
			false
		);

		$dependencies = array( 'square', 'formidable' );
		$script_url   = FrmSquareLiteAppHelper::plugin_url() . 'js/frontend.js';

		wp_enqueue_script(
			'formidable-square',
			$script_url,
			$dependencies,
			FrmAppHelper::plugin_version(),
			false
		);

		$square_vars = array(
			'formId'     => $form_id,
			'nonce'      => wp_create_nonce( 'frm_square_ajax' ),
			'ajax'       => esc_url_raw( FrmAppHelper::get_ajax_url() ),
			'settings'   => $action_settings,
			'appId'      => self::get_app_id(),
			'locationId' => self::get_location_id(),
			'style'      => self::get_style( $form_id ),
		);

		wp_localize_script( 'formidable-square', 'frmSquareVars', $square_vars );
	}

	/**
	 * Get the app ID for the Square app.
	 *
	 * @return string
	 */
	private static function get_app_id() {
		$mode = FrmSquareLiteAppHelper::active_mode();

		if ( 'live' === $mode ) {
			return 'sq0idp-eR4XI1xgNduJAXcBvjemTg';
		}

		return 'sandbox-sq0idb-MXl8ilzmhAgsHWKV9c6ycQ';
	}

	/**
	 * Get the location ID for the Square app.
	 *
	 * @return string
	 */
	private static function get_location_id() {
		return FrmSquareLiteConnectHelper::get_location_id();
	}

	/**
	 * @param int $form_id
	 *
	 * @return array
	 */
	private static function get_style( $form_id ) {
		$settings = self::get_style_settings_for_form( $form_id );
		$style    = array(
			'input'                     => array(
				'fontSize'        => $settings['field_font_size'],
				'color'           => $settings['text_color'],
				'backgroundColor' => $settings['bg_color'],
				'fontWeight'      => $settings['field_weight'],
			),
			'input::placeholder'        => array(
				'color' => $settings['text_color_disabled'],
			),
			'.input-container'          => array(
				'borderRadius' => self::get_border_radius( $settings ),
			),
			'.input-container.is-focus' => array(
				'borderColor' => $settings['border_color_active'],
			),
		);

		if ( ! empty( $settings['font'] ) ) {
			$style['input']['fontFamily'] = self::prepare_font_family_setting( $settings['font'] );
		}

		/**
		 * @since 6.22
		 *
		 * @param array $style
		 * @param array $settings
		 * @param int   $form_id
		 */
		return apply_filters( 'frm_square_style', $style, $settings, $form_id );
	}

	/**
	 * Prepare the font family setting for the Stripe element.
	 *
	 * @since 6.26
	 *
	 * @param string $font
	 *
	 * @return string
	 */
	private static function prepare_font_family_setting( $font ) {
		if ( ! str_contains( $font, ',' ) ) {
			return $font;
		}

		$fonts = explode( ',', $font );
		return trim( reset( $fonts ) );
	}

	/**
	 * Get the border radius for Stripe elements.
	 *
	 * @since 6.22
	 *
	 * @param array $settings
	 *
	 * @return string
	 */
	private static function get_border_radius( $settings ) {
		if ( ! empty( $settings['field_shape_type'] ) ) {
			switch ( $settings['field_shape_type'] ) {
				case 'underline':
				case 'regular':
					return '0px';
				case 'circle':
					return '30px';
			}
		}
		return $settings['border_radius'];
	}

	/**
	 * Get and format the style settings for JavaScript to use with the get_style function.
	 *
	 * @since 6.22
	 *
	 * @param int $form_id
	 *
	 * @return array
	 */
	private static function get_style_settings_for_form( $form_id ) {
		if ( ! $form_id ) {
			return array();
		}

		$style = FrmStylesController::get_form_style( $form_id );

		if ( ! $style ) {
			return array();
		}

		$settings   = FrmStylesHelper::get_settings_for_output( $style );
		$disallowed = array( ';', ':', '!important' );

		foreach ( $settings as $k => $s ) {
			if ( is_string( $s ) ) {
				$settings[ $k ] = str_replace( $disallowed, '', $s );
			}
		}

		return $settings;
	}

	/**
	 * If the names are being used on the CC fields,
	 * make sure it doesn't prevent the submission if Stripe has approved.
	 *
	 * @since 6.22
	 *
	 * @param array    $errors
	 * @param stdClass $field
	 * @param array    $values
	 *
	 * @return array
	 */
	public static function remove_cc_validation( $errors, $field, $values ) {
		$has_processed = ! empty( $_POST['square-token'] ) && ! empty( $_POST['square-verification-token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

		if ( ! $has_processed ) {
			return $errors;
		}

		$field_id = $field->temp_id ?? $field->id;

		if ( isset( $errors[ 'field' . $field_id . '-cc' ] ) ) {
			unset( $errors[ 'field' . $field_id . '-cc' ] );
		}

		if ( isset( $errors[ 'field' . $field_id ] ) ) {
			unset( $errors[ 'field' . $field_id ] );
		}

		return $errors;
	}

	/**
	 * @return void
	 */
	public static function actions_js() {
		wp_enqueue_script(
			'frm_square_admin',
			FrmSquareLiteAppHelper::plugin_url() . 'js/action.js',
			array( 'wp-hooks', 'wp-i18n' ),
			FrmAppHelper::plugin_version()
		);
	}
}