'stripe',
'action' => 'connect',
'mode' => $mode,
'state' => $state,
),
self::get_handler_url()
);
}
/**
* Handle the redirect back from the handler's intermediate page: verify the
* nonce, exchange the authorization code, and store the keys locally.
*/
public function maybe_handle_redirect() {
if ( ! is_admin() || wp_doing_ajax() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$provider = isset( $_GET['provider'] ) ? sanitize_text_field( wp_unslash( $_GET['provider'] ) ) : '';
if ( 'stripe' !== $provider ) {
return;
}
if ( ! current_user_can( forminator_get_admin_cap() ) ) {
return;
}
$mode = $this->get_redirect_mode();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$nonce = isset( $_GET['wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['wpnonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
$this->redirect_with_notice( 'error', __( 'Invalid security token. Please try connecting again.', 'forminator' ), $mode );
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$error = isset( $_GET['error'] ) ? sanitize_text_field( wp_unslash( $_GET['error'] ) ) : '';
if ( '' !== $error ) {
$this->redirect_with_notice( 'error', $this->humanize_error( $error ), $mode );
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
if ( '' === $code ) {
$this->redirect_with_notice( 'error', __( 'Stripe did not return an authorization code.', 'forminator' ), $mode );
return;
}
$result = $this->exchange_code( $code, $mode );
if ( is_wp_error( $result ) ) {
forminator_maybe_log( __METHOD__, $result->get_error_message() );
$this->redirect_with_notice( 'error', $result->get_error_message(), $mode );
return;
}
$this->redirect_with_notice( 'success', '', $mode );
}
/**
* Exchange the authorization code for connected-account keys and store them.
*
* @param string $code Authorization code from Stripe.
* @param string $mode Stripe mode: live or test.
*
* @return true|WP_Error
*/
public function exchange_code( $code, $mode = 'live' ) {
$mode = Forminator_Gateway_Stripe::normalize_oauth_mode( $mode );
$response = $this->handler_request(
array(
'action' => 'get_access_token',
'code' => $code,
'mode' => $mode,
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
if ( ! empty( $response->error ) ) {
return new WP_Error( 'forminator_stripe_oauth_failed', $this->humanize_error( (string) $response->error ) );
}
if ( empty( $response->publishableKey ) || empty( $response->secretKey ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
return new WP_Error(
'forminator_stripe_oauth_invalid_keys',
__( 'Stripe Connect handler did not return valid API keys.', 'forminator' )
);
}
$stripe_user_id = isset( $response->stripeUserId ) ? (string) $response->stripeUserId : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$secret_key = (string) $response->secretKey; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$save_mode = ! empty( $response->mode ) ? Forminator_Gateway_Stripe::normalize_oauth_mode( (string) $response->mode ) : $mode;
$account_name = Forminator_Gateway_Stripe::resolve_connected_account_name( $stripe_user_id, $secret_key );
Forminator_Gateway_Stripe::store_oauth_credentials(
$save_mode,
$response->publishableKey, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$secret_key,
$stripe_user_id,
$account_name
);
return true;
}
/**
* Disconnect a mode by clearing this site's local credentials only.
*
* We deliberately do NOT deauthorize the account on the Stripe platform.
* The same connected account can be shared across many sites through the
* same handler (platform), and an OAuth deauthorize revokes the platform's
* access to that account globally - which would break every other site
* still using it. Removing the local credentials disconnects only this
* site; platform access can still be revoked from the Stripe Dashboard.
*
* @param string $mode Stripe mode: live or test.
*
* @return void
*/
public function disconnect( $mode ) {
$mode = Forminator_Gateway_Stripe::normalize_oauth_mode( $mode );
Forminator_Gateway_Stripe::disconnect_oauth( $mode );
}
/**
* Read Stripe mode from the redirect query args.
*
* @return string
*/
protected function get_redirect_mode() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- mode only; nonce verified in maybe_handle_redirect().
$mode = isset( $_GET[ self::CALLBACK_QUERY_MODE ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::CALLBACK_QUERY_MODE ] ) ) : 'live';
return Forminator_Gateway_Stripe::normalize_oauth_mode( $mode );
}
/**
* Redirect to settings with a clean URL plus success or error query args.
*
* Error messages are sanitized once here before being added to the query string.
*
* @param string $status success or error.
* @param string $message Optional error message.
* @param string $mode Stripe mode: live or test.
*/
protected function redirect_with_notice( $status, $message = '', $mode = 'live' ) {
$mode = Forminator_Gateway_Stripe::normalize_oauth_mode( $mode );
$redirect_url = remove_query_arg(
array(
self::CALLBACK_QUERY_MODE,
self::CALLBACK_QUERY_ERROR,
self::CALLBACK_QUERY_SUCCESS,
'provider',
'action',
'code',
'error',
'error_description',
'wpnonce',
'domain',
),
self::get_settings_page_url()
);
if ( 'success' === $status ) {
$redirect_url = add_query_arg(
array(
self::CALLBACK_QUERY_SUCCESS => '1',
self::CALLBACK_QUERY_MODE => $mode,
),
$redirect_url
);
} else {
$error_param = '' !== $message ? $message : $status;
$redirect_url = add_query_arg(
array(
self::CALLBACK_QUERY_ERROR => sanitize_text_field( $error_param ),
self::CALLBACK_QUERY_MODE => $mode,
),
$redirect_url
);
}
wp_safe_redirect( esc_url_raw( $redirect_url ) );
exit;
}
/**
* GET a Stripe action from the handler `provider` endpoint.
*
* @param array