minter = $minter ?? new KeyMinter(); $this->client = $client ?? new ConnectClient(); } /** * @return string 'connected', 'busy', or a failure reason. */ public function connect( string $code, int $user_id ): string { $code = trim( $code ); if ( 1 !== preg_match( '/^[A-Za-z0-9_-]{16,128}$/', $code ) ) { return $this->fail( 'invalid_code_format' ); } $blocker = Config::connect_blocker(); if ( null !== $blocker ) { // Not stored as last_error: the live blocker notice on the connect screen already says it, and a // stored copy would outlive the merchant fixing the setting. return $blocker; } if ( Options::is_connected() ) { return $this->fail( 'already_connected' ); } if ( ! Options::acquire_connect_lock() ) { return 'busy'; } try { // Re-checked under the lock: an attempt that held the lock may have connected since the check above, // and minting now would overwrite that connection and orphan its live key. if ( Options::is_connected() ) { return $this->fail( 'already_connected' ); } $this->minter->revoke_pending(); try { $key = $this->minter->mint( $user_id ); } catch ( ConnectException $e ) { Log::error( 'Connect failed: ' . $e->getMessage() ); return $this->fail( $e->reason() ); } Options::save_pending_key_id( $key['key_id'] ); try { $reply = $this->client->complete( $code, $key ); } catch ( \Throwable $e ) { $this->minter->revoke_pending(); $reason = $e instanceof ConnectException ? $e->reason() : 'unreachable'; Log::warning( 'Connect failed, key revoked: ' . $e->getMessage() ); return $this->fail( $reason ); } Options::save_connection( $reply['tenant_id'], $key['key_id'], $reply['webhook_secret'], $reply['pixel_id'] ); Options::clear_pending_key_id(); Options::reset_settings_for_new_connection(); Options::clear_last_error(); Log::info( 'Connected as ' . $reply['tenant_id'] ); // Each in its own try: a throw scheduling one must not leave the store without the other. try { ProductSync::schedule(); } catch ( \Throwable $e ) { Log::error( 'Connected, but scheduling the product sync failed: ' . $e->getMessage() ); } try { SyncSchedule::schedule(); } catch ( \Throwable $e ) { Log::error( 'Connected, but scheduling the hourly approved-product sync failed: ' . $e->getMessage() ); } return 'connected'; } finally { Options::release_connect_lock(); } } private function fail( string $reason ): string { Options::set_last_error( $reason, Messages::for_reason( $reason ) ); return $reason; } }