Why does Deadlock occur for this INSERT/UPDATE combination despite seemingly holding an X lock?
Can some or all of the 4 steps be replaced by a single query:

INSERT ... ON DUPLICATE KEY UPDATE ...

or maybe

INSERT IGNORE ... and check rows_affected?

It would be better if you showed us all the SQL between BEGIN and COMMIT.

Are you doing any time-consuming processing between BEGIN and COMMIT?  Can any of it be moved out.  The faster the transaction runs, the less likely it will deadlock.

Do you have code to rerun the transaction when it deadlocks?  This a useful "last resort".  (It is not yet obvious whether the particular deadlock can always be avoided.)