About using ethers to sign EIP-191

Related methods in this contract

        bytes32 criteriaMessageHash = getMessageHash(
            _type,
            paymentErc20,
            price
        );

        bytes32 ethSignedMessageHash = getEthSignedMessageHash(
            criteriaMessageHash
        );

        require(
            recoverSigner(ethSignedMessageHash, signature) == userAddress,
            "ThetanBoxPayment: invalid seller signature"
        );


    function getMessageHash(
        uint256 _boxType,
        address _paymentErc20,
        uint256 _price
     ) public pure returns (bytes32) {
        
        return keccak256(abi.encodePacked(_boxType, _paymentErc20, _price));
    }

    function getEthSignedMessageHash(bytes32 _messageHash)
        public
        pure
        returns (bytes32)
    {
        /*
        Signature is produced by signing a keccak256 hash with the following format:
        "\x19Ethereum Signed Message\n" + len(msg) + msg
        */
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }


Try to use ethers API to encode

  const boxId = 1;
  const price = 10;
  const paymentErc20 = "0x0000000000000000000000000000000000000000";

  const getMessageHash = await BoxHubContract.getMessageHash(boxId,paymentErc20,price);
  console.log("getMessageHash ",getMessageHash);
  
   const getMessageHash2  = await ethers.utils.keccak256(hre.ethers.utils.defaultAbiCoder.encode([ "uint256", "address" , "uint256" ], [ boxId, paymentErc20 , price ]))
  console.log("getMessageHash 2 ",getMessageHash2);

Test getMessageHash after encoding! = getMessageHash2 , It seems that I use the normal API, help brother

This is because the contract uses encodePacked, whereas in JS you're using the equivalent of encode. Since address is a 20-byte type, this will result in different hashes.

The correct ethers function to use here is solidityPack.