the “bind to the fetch completing rather than the button click” bit in #4 is the right instinct but fetch isnt the only transport you have to cover. plenty of themes and a fair few apps still add through XMLHttpRequest, so if you only patch fetch those adds are invisible to you and the drawer just never opens. you end up patching XHR as well, both open, to stash the url, and send, to hang a loadend listener on it.
then theres the third path, the plain form post with no ajax at all. listening isnt enough there, because ajax themes install their own submit handler and yours runs after theirs — a motion style theme will have already sent the shopper to /cart by the time you react. taking it over means a capture phase listener on document with preventDefault and stopPropagation, and then you do the post yourself.
two traps show up the moment you have all three running. one, a single add can trip more than one detector, so you resync twice and the drawer opens twice — worth putting a single claim token per mutation cycle in front of it, first detector wins and the others no-op. two, your own post goes out through the fetch you just patched, so you re-enter your own interceptor. grab a reference to the native fetch before you patch it and use that one internally.
on selling_plan its a bit worse than “include it or the add fails”. some themes render several selling_plan inputs, one per plan, with the inactive ones empty. read the form naively and you pick up an empty one, the add succeeds, and the subscription quietly becomes a one-time purchase, with no error anywhere to tell you. take the last non-empty value.
last thing, less js and more scope: buy it now and shop pay on the product page skip the cart entirely, so nothing you build in the drawer runs for those shoppers at all. thats fine, just size it before you promise anyone cart level offers.
disclosure, i build verve, a cart drawer app, so most of the above is scar tissue from other peoples themes rather than theory.