namespace WPForms\Integrations\Square\Admin;
use WPForms\Integrations\Square\Api\Api;
use WPForms\Vendor\Square\Models\Location;
use WPForms\Vendor\Square\Models\LocationCapability;
use WPForms\Vendor\Square\Models\LocationStatus;
use WPForms\Vendor\Square\Environment;
use WPForms\Admin\Notice;
use WPForms\Helpers\Transient;
use WPForms\Integrations\Square\Connection;
use WPForms\Integrations\Square\Helpers;
use WPForms\Integrations\Square\Api\WebhooksManager;
* Square Connect functionality.
private const WPFORMS_URL = 'https://wpforms.com';
private $webhooks_manager;
$this->webhooks_manager = new WebhooksManager();
private function hooks() {
add_action( 'admin_init', [ $this, 'handle_actions' ] );
add_action( 'wpforms_square_refresh_connection', [ $this, 'refresh_connection_schedule' ] );
add_action( 'wp_ajax_wpforms_square_refresh_connection', [ $this, 'refresh_connection_manual' ] );
add_action( 'wp_ajax_wpforms_square_create_webhook', [ $this->webhooks_manager, 'connect' ] );
public function handle_actions() {
if ( ! wpforms_current_user_can() || wp_doing_ajax() ) {
$this->validate_scopes();
isset( $_GET['_wpnonce'] ) &&
wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_square_disconnect' )
$this->handle_disconnect();
$this->schedule_refresh();
! empty( $_GET['state'] ) &&
isset( $_GET['square_connect'] ) &&
$_GET['square_connect'] === 'complete'
$this->handle_connected();
* Validate connection scopes.
private function validate_scopes() {
if ( Helpers::is_license_ok() ) {
$connection = Connection::get();
if ( ! $connection || ! $connection->is_configured() ) {
// Bail early if currency is not supported for applying a fee.
if ( ! Helpers::is_application_fee_supported() ) {
if ( $connection->get_scopes_updated() ) {
// Revoke tokens if the license is not valid and scopes are missing.
$connection->revoke_tokens();
* Handle a successful connection.
private function handle_connected() {
$state = sanitize_text_field( wp_unslash( $_GET['state'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
$connection_raw = $this->fetch_new_connection( $state );
$connection = $this->maybe_save_connection( $connection_raw );
$mode = $connection->get_mode();
// Sync the Square settings mode with a connection mode.
Helpers::set_mode( $mode );
$this->prepare_locations( $mode );
$redirect_url = Helpers::get_settings_page_url();
if ( ! $connection->is_usable() ) {
$redirect_url .= '#wpforms-setting-row-square-heading';
wp_safe_redirect( $redirect_url );
private function handle_disconnect() {
$live_mode = isset( $_GET['live_mode'] ) ? absint( $_GET['live_mode'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$mode = $live_mode ? Environment::PRODUCTION : Environment::SANDBOX;
$connection = Connection::get( $mode );
if ( Helpers::is_production_mode() ) {
$this->unschedule_refresh();
if ( Helpers::is_webhook_enabled() ) {
Helpers::reset_webhook_configuration( true );
Helpers::set_locataion_id( '', $mode );
Helpers::detete_transients( $mode );
wp_safe_redirect( Helpers::get_settings_page_url() );
* Handle refresh connection triggered by AS task.
public function refresh_connection_schedule() {
// Don't run refresh tokens for Sandbox connection.
if ( Helpers::is_sandbox_mode() ) {
$connection = Connection::get();
// Check connection and cancel AS task if connection is not exists, broken OR already invalid.
if ( ! $connection || ! $connection->is_configured() || ! $connection->is_valid() ) {
$this->unschedule_refresh();
// If connection is not expired, we'll just fetch active locations.
if ( ! $connection->is_expired() ) {
$this->prepare_locations( $connection->get_mode() );
// If connection is expired, try to refresh tokens.
$connection = $this->try_refresh_connection( $connection );
if ( is_wp_error( $connection ) ) {
// If connection is invalid, we'll cancel AS task.
if ( $connection && ! $connection->is_valid() ) {
$this->unschedule_refresh();
// Make sure and check connection tokens through fetching active locations.
$this->prepare_locations( $connection->get_mode() );
* Handle refresh connection triggered manually.
public function refresh_connection_manual() {
// Security and permissions check.
! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ||
! wpforms_current_user_can()
wp_send_json_error( esc_html__( 'You are not allowed to perform this action', 'wpforms-lite' ) );
$error_general = esc_html__( 'Something went wrong while performing a refresh tokens request', 'wpforms-lite' );
if ( empty( $_POST['mode'] ) ) {
wp_send_json_error( $error_general );
$mode = sanitize_key( $_POST['mode'] );
$connection = Connection::get( $mode );
if ( ! $connection || ! $connection->is_configured() ) {
wp_send_json_error( $error_general );
// Try to refresh connection.
$connection = $this->try_refresh_connection( $connection );
if ( is_wp_error( $connection ) ) {
$error_specific = $connection->get_error_message();
$error_message = empty( $error_specific ) ? $error_general : $error_general . ': ' . $error_specific;
wp_send_json_error( $error_message );
$this->prepare_locations( $mode );
* Try to refresh connection.
* @param Connection $connection Connection object.
* @return Connection|WP_Error
private function try_refresh_connection( $connection ) {
$response = $this->fetch_refresh_connection( $connection->get_refresh_token(), $connection->get_mode() );
if ( is_wp_error( $response ) ) {
if ( $response->get_error_code() === 'refresh_connection_fail' && $connection->is_valid() ) {
->set_status( Connection::STATUS_INVALID )
$refreshed_connection = $this->maybe_save_connection( $response, true );
return $refreshed_connection ?? new WP_Error();
* Schedule the connection refresh.
private function schedule_refresh() {
* Allow modifying a condition check where the AS task will be registered.
* @param int $interval The refresh interval.
if ( (bool) apply_filters( 'wpforms_square_admin_connect_schedule_refresh_prevent_task_registration', ! wpforms_is_admin_page() ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$tasks = wpforms()->obj( 'tasks' );
if ( is_null( $tasks ) ) {
if ( $tasks->is_scheduled( 'wpforms_square_refresh_connection' ) !== false ) {
// Register AS task only if a Production connection exists.
if ( ! Connection::get( Environment::PRODUCTION ) ) {
* Filter the frequency with which the OAuth connection should be refreshed.
* @param int $interval The refresh interval.
$interval = (int) apply_filters( 'wpforms_square_admin_connect_schedule_refresh_interval', 12 * HOUR_IN_SECONDS ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$tasks->create( 'wpforms_square_refresh_connection' )
->recurring( time() + $interval, $interval )
* Unschedule the connection refresh.
private function unschedule_refresh() {
// Exit if AS function does not exist.
if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
as_unschedule_all_actions(
'wpforms_square_refresh_connection',
[ 'tasks_meta_id' => null ],
* Check connection raw data and save it if everything is OK.
* @param array $raw Connection raw data.
* @param bool $silent Optional. Whether to prevent showing admin notices. Default false.
* @return Connection|null
private function maybe_save_connection( array $raw, bool $silent = false ) {
$connection = new Connection( $raw, false );
// Bail if a connection doesn't have required data.
if ( ! $connection->is_configured() ) {
'We could not connect to Square. No tokens were given.',
'type' => [ 'payment', 'error' ],
) : Notice::error( esc_html__( 'Square Error: We could not connect to Square. No tokens were given.', 'wpforms-lite' ) );
// Prepare connection for save.
// Bail if a connection is not ready for save.
if ( ! $connection->is_saveable() ) {
'We could not save an account connection safely. Please, try again later.',
'type' => [ 'payment', 'error' ],
) : Notice::error( esc_html__( 'Square Error: We could not save an account connection safely. Please, try again later.', 'wpforms-lite' ) );
* Prepare Square business locations.
* @param string $mode Square mode.
private function prepare_locations( string $mode ): array {
$locations = $this->fetch_locations( $mode );
if ( $locations === null ) {
$this->reset_location( $mode );
Transient::delete( 'wpforms_square_active_locations_' . $mode );
$locations = $this->active_locations_filter( $locations );
if ( empty( $locations ) ) {
$this->reset_location( $mode );
Transient::set( 'wpforms_square_active_locations_' . $mode, [] );
$this->set_location( $locations, $mode );
Transient::set( 'wpforms_square_active_locations_' . $mode, $locations );
* Fetch Square business locations.
* @param string $mode Square mode.
private function fetch_locations( string $mode ) {
$connection = Connection::get( $mode );
$api = new Api( $connection );
$locations = $api->get_locations();
->set_status( Connection::STATUS_INVALID )