Solidity v0.6.0. Fallback functions. What are they needed for?

After reading the documentation for Solidity v0.6.0 docs, I still don't understand the meaning of the fallback functions. I read that it was split into 2 functions: fallback () external payable and receive () external payable. That they are anonymous and do not accept any parameters, and in the overwhelming majority of cases, receive () external payable is used to receive funds. Can you please explain with the example of my code, some use cases for these functions, in order to understand all their features, otherwise somehow everything is in a vacuum, but I understand that this is an important concept? Even the meaning of the receive () external payable function is not clear, in which I call on the buyToken () method, why is it needed if I call on the buyToken () in the Remix directly, bypassing the receive () external payable since she is not visible and anonymous.

pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT
    
    contract BuyToken {
      mapping(address => uint256) public balances;
      address payable wallet;
    
      event Purchase(
        address indexed buyer,
        uint256 amount
      );
    
      constructor(address payable _wallet) {
        wallet = _wallet;
      }
    
    
      fallback() external payable {
      }
    
    
      receive() external payable {
        buyToken();
      }
    
      function buyToken() public payable {
        balances[msg.sender] += 1;
        wallet.transfer(msg.value);
        emit Purchase(msg.sender, 1);
      }
    }

Solution 1:

When the sender sends ETH to your contract address and doesn't specify any function (i.e. the data field of the tx is empty), the receive() gets executed.

Since the receive() just calls buyToken(), it produces the same set of actions as if the user executed the buyToken() directly.

But other contracts can make a different use of the receive() function. Example of a simple bank contract:

pragma solidity ^0.8;

contract MyContract {
    mapping (address => uint256) public balances;

    receive() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 _amount) external {
        require(_amount <= balances[msg.sender], 'Insufficient balance');
        balances[msg.sender] -= _amount;
        payable(msg.sender).transfer(_amount);
    }
}

Or a timelock:

pragma solidity ^0.8;

contract MyContract {
    uint256 public constant unlockAfter = 1640995200; // 2022-01-01

    receive() external payable {
        // anyone can send funds to this contract
    }

    function withdraw() external {
        require(msg.sender == address(0x123), 'Not authorized');
        require(block.timestamp >= unlockAfter, 'Not unlocked yet');
        payable(msg.sender).transfer(address(this).balance);
    }
}

The fallback() functions is used when the function signature (first 4 bytes of the data field) doesn't match any of the existing functions.

pragma solidity ^0.8;

contract MyContract {
    function foo() external {
        // executed when the `data` field starts with `0xc2985578`, the signature of `foo()`
    }
    
    fallback() external {
        // executed when the `data` field is empty or starts with an unknown function signature
    }
}

Solution 2:

I'm not sure about your code example but here it goes:
Fallback function - I think here is a good explanation. So if not marked payable, it will throw exception if contract receives plain ether without data.
External payable - This post explains External well. So it costs less gas to call external than public. Only in your example it would make sense to change buyToken() from "public" to "external". Far as I understand there is no benefit to call public from external...