There is workaround for simple scenarios - set exclusive flag for async step. Every critical section should be with exclusive flag, this way asyncExecutor locks process before execution writing timestamp of seizure. After each "critical" step use another async step, so process will be presisted right after successful booking of ticket.
For long calls you may need to increase timerLockTimeInMillis, asyncJobLockTimeInMillis timers in asyncExecutor settings. Default timers can be not enough for some long thinking services.
Exclusive flag does not reduce overall performance of high load system, since overall resources are utilized. It does increase time for execution for particular processes, but while one process is doing its job in one thread, another thread can be used by another process.
There is another possibility - cache calls to outer services, stall/reject calls with identical parameters if there is one in progress, save results in separate DB on successful call, read result for second identical call instead of proceeding to outer service. You need to make proxies for outer services, make separate DB, design requests so that they allow you to identify if the request is repeated attempt or not.
The main drawback here is excessive successful request if later your process fails.
The proposed way to do processes is to make every service you use transactional and properly add rollback steps in process. So every time your second booking process execution fails, it calls corresponding booking web service to cancel excessive reservation. It's good idea to use this approach with exclusive flags anwyay.
Retry for non-webservice tasks can happen naturally if you do DB manipulations in the same transaction as for activiti process, for example.
P.S. MySql deadlock is not supposed to happen here, though.