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