Handling User Minting of NFT Collection

Hello everyone I'm currently working on my first NFT collection for a generative art game. Now I'm trying to figure out the best way to go about having users mint their own tokens. The problem that I'm trying to solve is how restrict which token uri gets inputted upon minting. My solution to this would be to have a backend that handles the minting requests from the user. That way I can control which token uri gets used when minting the token. However, this makes it so that I can't set the minting function to payable. So instead I was thinking of creating two separate methods. One to take a request for minting a token which then triggers an event, then another method that gets called from the backend upon receiving the user mint event. The first function that the user calls to request a mint would be set to payable. Looking for some feedback on this approach or if anyone has a better flow to how this would be handled. Thanks!

  /**
   * @dev Mints the actual token
   * Relies on userMint method to take payment for minting before actually minting the token
   */
  function mintUserToken(address to, uint256 tokenId, string memory uri) external onlyOwner {
    require(to == tokenIdToUserMinted[tokenId].minter, "Token Id must match user.");
    require(tokenId == tokenIdToUserMinted[tokenId].tokenId, "Token Id does not match.");
    require(false == tokenIdToUserMinted[tokenId].minted, "Token already minted.");

    tokenIdToUserMinted[tokenId].minted = true;

    _safeMint(to, tokenId);
    _setTokenURI(tokenId, uri);
  }

  /**
   * @dev User Mint
   * Anyone can call this function
   */
  function userMint() external payable {
    require(msg.value >= _basePrice, "Not enought Matic sent");
    require(_totalSupply < _maxSupply, "Max supply reached.");

    _totalSupply++;
    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();

    UserMinted memory minted = UserMinted(msg.sender, tokenId, false);
    tokenIdToUserMinted[tokenId] = minted;

    emit ToMint(msg.sender, tokenId);
  }

Based on the tokenID, the tokenURI method points to the metadata of the specific token, which contains information about the token's attributes.

Thanks for the response! I think that's the most straightforward solution to this. So just to reiterate to make sure I'm getting this right. The contract will have a base_uri and then upon minting it will attach the token id to that base uri to associate it with a json that's already uploaded.