UPDATE: The authorization for the system contract to do transfers in behalf of the users through inline actions is automatic because it is configured as a privileged account in the blockchain. For normal contracts to be able to send inline actions to other contracts, you would need the user to specify the
eosio.code permission for that contract, thus giving explicit authorization for that contract to do a transfer for them, and allowing them to view and sign the specific terms of your contract and its ricardian text, so this might be what you are looking for. More info here: https://github.com/EOSIO/eos/issues/3847#issuecomment-394805728
I personally think that reacting to
require_recipient should be used more for each user automating actions related to their own account in response to certain actions related to it, but not for providing functionality to the users of your smart contract.
For what you want to do I would recommend making a contract with which users will interact directly, sending a transaction to the specific action of your contract, so they can read the ricardian contract that is specific to that action. Then you can use the permission given by the user in the transaction itself to issue the
eosio.token::transfer action from your own contract code. You can use
SEND_INLINE_ACTION for this purpose, and you can see an example of it happening in the system contract's
bidname action: https://github.com/EOSIO/eos/blob/master/contracts/eosio.system/eosio.system.cpp#L107
Here, the user accepts the terms of the
bidname action, which include an automated transfer of funds triggered by that inline action call.
This approach is actually more secure, since if a user issues a regular transfer to the contract account, but the contract's code is updated before the transfer gets processed by the network, and the update changes the behavior of the contract upon receiving a transfer (maybe the contract doesn't react to transfers at all anymore) then the user's transaction will still go through and the funds will be transferred with no side-effect or a different one than the user expected. The smart contract's account has no "legal" obligation to do anything in response to the transfer, since no agreement was made between the two parties, so the user has to trust that the code will be there to respond in a certain way to their transfer.
On the other hand, if instead the user signs a transaction agains a specific action of a contract, they are signing those contractual terms specifically, and the contract's account has explicit obligation of abiding to those terms. Also if the contract's code was updated just before the user's transfer is processed, the transaction would fail since the signature would be valid for a previous version of the contract, not the current one.
This and other reasons are why the Block.one team has decided to implemented these important things using inline transactions all around the core functionalities of EOSIO, like when buying/selling RAM, staking and delegating bandwidth, and much more: https://github.com/EOSIO/eos/blob/master/contracts/eosio.system/delegate_bandwidth.cpp