Keeping that kind of logic in a stored procedure is a good choice. Try to implement that logic in triggers and in some way or another you will end up looking at 
ORA-04091: table <your table> is mutating, trigger/function may not see it
For example having implemented that in a trigger will allow you to use insert like
insert into <Yourtable> (<col1>...<coln>)
   values(val1... valn)
But you will definitely get a mutating table error if you try to execute insert like this one
 insert into <Yourtable> (<col1>...<coln>)
    select col1, .. coln 
      from some_table
If you  do not want to use a stored procedure, your other choice might be a view and instead of trigger
Create or replace view <ViewName> as
  select *
    from your_table;
 create or replace trigger <Trigger_name> 
   instead of insert on <View_Name>
 begin
   -- your logic
 end;
IN ADDITION
Also to enforce business rules though constraints (CHECK constraint), you can combine CHECK constraints and materialized view as follows:
Create materialized view (do not forget to create materialized view log before) for your table that would query data violating your business rules constraints.
 Create materialized view MV_1
  refresh complete on commit as
  --here goes your query
  -- selecting data you want to raise exception on
Add check constraint that will always be false. Something like this
alter table MV_1 add constraint CHK check(1=2) deferrable;
Having done that you will get check constraint <..> violated when you try to insert data in your table that violate your business rule constraints.