namespace WPForms\Integrations\Stripe\Admin;
use WPForms\Integrations\Stripe\Api\DomainManager;
use WPForms\Integrations\Stripe\Api\WebhooksManager;
use WPForms\Integrations\Stripe\Helpers;
use WPForms\Integrations\Stripe\WebhooksHealthCheck;
use WPForms\Vendor\Stripe\Account;
* Stripe Connect functionality.
* WPForms Stripe OAuth URL.
const WPFORMS_URL = 'https://wpforms.com/oauth/stripe-connect';
* Stripe live/test account objects.
protected $accounts = [];
private $webhooks_manager;
$this->webhooks_manager = new WebhooksManager();
$this->domain_manager = new DomainManager();
private function hooks() {
add_action( 'admin_init', [ $this, 'handle_oauth_handshake' ] );
* Handle Stripe Connect OAuth handshake and save Stripe keys.
public function handle_oauth_handshake(): void {
// Handle disconnect requests.
if ( $this->is_valid_disconnect_request() ) {
$this->handle_disconnect();
if ( ! $this->is_valid_handshake_request() ) {
$state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$credentials = $this->fetch_stripe_credentials( $state );
$required_keys = [ 'stripe_user_id', 'stripe_publishable_key', 'access_token', 'refresh_token', 'live_mode' ];
if ( 0 !== count( array_diff( $required_keys, array_keys( $credentials ) ) ) ) {
$mode = empty( $credentials['live_mode'] ) ? 'test' : 'live';
$this->set_connected_user_id( $credentials['stripe_user_id'], $mode );
$this->set_current_mode( $mode );
// In case of switching accounts existing account data needs to be cleared.
unset( $this->accounts[ $mode ] );
Helpers::set_stripe_key( $credentials['stripe_publishable_key'], 'publishable', $mode );
Helpers::set_stripe_key( $credentials['access_token'], 'secret', $mode );
$this->update_account_meta( $credentials['stripe_user_id'], $mode );
$this->set_connected_account_country( $mode );
$this->webhooks_manager->connect();
$this->domain_manager->validate();
$settings_url = $this->get_payments_settings_url( false );
wp_safe_redirect( $settings_url );
* Validates if the current handshake request is valid.
private function is_valid_handshake_request(): bool {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_stripe_connect' ) ) {
if ( ! isset( $_GET['stripe_connect'] ) || $_GET['stripe_connect'] !== 'complete' ) {
if ( ! wpforms_current_user_can() ) {
* Validates if the current disconnect request is valid.
private function is_valid_disconnect_request(): bool {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_stripe_disconnect' ) ) {
if ( ! wpforms_current_user_can() ) {
* Fetch Stripe credentials from https://wpforms.com.
* @param string $state Anonymous autogenerated ID to safely fetch Stripe credentials.
protected function fetch_stripe_credentials( $state ) {
$response = wp_remote_post(
'action' => 'credentials',
if ( is_wp_error( $response ) ) {
$body = wpforms_json_decode( wp_remote_retrieve_body( $response ), true );
return is_array( $body ) ? $body : [];
* Fetch Stripe Account from Stripe.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
protected function fetch_stripe_account( $mode = '' ) {
$api_key = Helpers::get_stripe_key( 'secret', $mode );
$account = Account::retrieve( null, sanitize_text_field( $api_key ) );
} catch ( \Exception $e ) {
* Update connected account meta.
* @param string $account_id Account ID.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
public function update_account_meta( $account_id = '', $mode = '' ) {
$mode = Helpers::get_stripe_mode();
// Stripe API has limited update method for live accounts only.
if ( $mode !== 'live' ) {
$account_id = $this->get_connected_user_id( $mode );
// Return early if no connected account.
$licence_type = wpforms_get_license_type();
'wpforms_stripe' => Helpers::is_addon_active() ? 'addon' : 'core',
'wpforms_type' => wpforms()->is_pro() ? 'pro' : 'lite',
'wpforms_license' => $licence_type ? $licence_type : 'lite',
Account::update( $account_id, [ 'metadata' => $metadata ], Helpers::get_auth_opts() );
} catch ( \Exception $e ) {
'Unable to update connected Stripe account meta.',
'type' => [ 'payment', 'error' ],
* Generate random alphanumeric token string.
* Token length is always 32 chars.
public function generate_random_token() {
if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
$strong_result = false; // This has been added as argument #2 ($strong_result) cannot be passed by reference.
$random = openssl_random_pseudo_bytes( 16, $strong_result );
if ( $random === false ) {
return bin2hex( $random );
* Set fetched Stripe Account for caching purposes.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
protected function set_connected_account( $mode = '' ) {
$user_id = $this->get_connected_user_id( $mode );
$account = $this->fetch_stripe_account( $mode );
$this->accounts[ $mode ] = null;
if ( ! isset( $account->id ) || $account->id !== $user_id ) {
$this->accounts[ $mode ] = $account;
* Get cached Stripe Account or fetch it from Stripe.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
public function get_connected_account( $mode = '' ) {
$mode = Helpers::validate_stripe_mode( $mode );
if ( empty( $this->accounts ) || ( is_array( $this->accounts ) && ! array_key_exists( $mode, $this->accounts ) ) ) {
$this->set_connected_account( $mode );
if ( ! empty( $this->accounts[ $mode ] ) ) {
return $this->accounts[ $mode ];
* Save connected user id to an option.
* @param string $user_id User id to set.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
protected function set_connected_user_id( $user_id, $mode = '' ) {
$mode = Helpers::validate_stripe_mode( $mode );
return update_option( "wpforms_stripe_{$mode}_connect_user_id", sanitize_text_field( $user_id ) );
* Get saved Stripe Connect user id from DB.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
public function get_connected_user_id( $mode = '' ) {
$mode = Helpers::validate_stripe_mode( $mode );
$user_id = get_option( "wpforms_stripe_{$mode}_connect_user_id", '' );
* User ID associated with the Stripe account.
* @param string $user_id User ID.
return (string) apply_filters( 'wpforms_stripe_admin_connect_get_connected_user_id', $user_id ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Get Stripe Account name.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
public function get_connected_account_name( $mode = '' ) {
$account = $this->get_connected_account( $mode );
if ( isset( $account->display_name ) ) {
return $account->display_name;
if ( isset( $account->settings, $account->settings->dashboard->display_name ) ) {
return $account->settings->dashboard->display_name;
* @param string $mode Stripe mode (e.g. 'live' or 'test').
private function set_current_mode( $mode ) {
$key = 'stripe-test-mode';
$settings = (array) get_option( 'wpforms_settings', [] );
$settings[ $key ] = $mode === 'test';
update_option( 'wpforms_settings', $settings );
* Set Stripe Account country.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
private function set_connected_account_country( $mode = '' ) {
$account = $this->get_connected_account( $mode );
if ( ! isset( $account->country ) ) {
update_option( "wpforms_stripe_{$mode}_account_country", strtolower( $account->country ) );
* Get Stripe Connect button URL.
* @param string $mode Stripe mode (e.g. 'live' or 'test').
public function get_connect_with_stripe_url( $mode = '' ) {
$mode = Helpers::validate_stripe_mode( $mode );
$settings_url = $this->get_payments_settings_url();
'live_mode' => absint( $mode === 'live' ),
'state' => $this->generate_random_token(),
'site_url' => rawurlencode( $settings_url ),
* Get "Payments" settings page URL.
* @since 1.9.8 Added `$include_nonce` and `$action` parameters to allow more flexible settings.
* @param bool $include_nonce Whether to include nonce in the URL.
* @param string $action Action to be used for nonce verification.
private function get_payments_settings_url( bool $include_nonce = true, string $action = 'wpforms_stripe_connect' ): string {
'page' => 'wpforms-settings',
$args['_wpnonce'] = wp_create_nonce( $action );
return add_query_arg( $args, admin_url( 'admin.php' ) );