Let me put a bold statement out there: don't use transactions across microservices.
Transactions work amazingly in a single node application, like a relational database. They are quite complicated when stuff gets distributed.
The complexity comes from all edge cases a distributed system needs to handle. When a node or a microservice goes offline (or disconnects from the rest of the network) the system as whole needs to decide on what to do next. More or less there are two options - sacrifice correctness or availability. This is what CAP theorem is about.
Putting transactions aside, I recommend to do a two step process: first the sales service reserves an item and then the service confirms a purchase.
It makes sense to pay attention to failure scenarios: for example, what will happen if an item got reserved, but never purchased or cancelled - because the sales process failed.
It seems that a timeout would be a good idea - if an item is not updated (purchased or cancelled) in a given interval, then assume it is cancelled. This is a dangerous path to take. It is a total possibility, that the sales process if frozen and it may unfreeze after the timeout. This will put your system in an inconsistent state - an item might be sold twice.
A better approach would be a service which will check for reserved items and do extra steps to make sure it is safe to release them. It does not has to be a dedicated service, maybe a process in an existing one.
In either case, with the correctness being the goal - we absolutely don't want to double sell items - your system will sacrifice some availability. But having the clean up process as a dedicated flow, will minimize the availability hit.