I would like my checkout page to default to them if the customer actualy uses them, but not otherwise. Defaulting to an Apple/Google Pay button when it's not set up is a dark pattern that they are using to try and enlist me into advertising their service to non-users.
Apple Pay: This snippet gives a "yes" response if you are using iOS, or if you are using Safari on desktop, regardless of whether you have given Apple your credit card:
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
var promise = ApplePaySession.canMakePaymentsWithActiveCard(...);
promise.then (function (canMakePayments) { ... }); // Yes or No
} else {
// No
}
Google Pay: This snippet gives a "yes" in every browser, even if you are not logged in to Google at all, let alone have given them your credit card:
goog = new google.payments. api. PaymentsClient ({ ... });
goog.isReadyToPay ({ ... }).then (function(response) {
if (response.result) ... // Yes
else ... // No
});
This is some strange definition of "is ready to pay" that could more accurately be described as "is ready to create an account on a completely other web site first".
I think it's none of your beeswax whether I've signed up for one of these * Pay services, not until I've chosen one of them for this transaction (if I have an account with you and you want to remind me which payment method I used last time, that's fine).
If you want to reduce the advertisement aspect, you can hide them behind another click on a "more payment options" button or something but in general I don't think that would be to the benefit of users.
If they did let you know automatically, that would make fingerprinting easier :-( . Odds are if you find a method, it may suddenly break on you in the future.
ApplePay: canMakePaymentsWithActiveCard is the one you want. If the user has “allow websites to check for Apple Pay” enabled in Safari, this will actually check for a card in their wallet. If they have that setting disabled for privacy reasons then it will always resolve with a “yes, they have a card.” The idea being, ostensibly, if you have an iThing and turned off that setting for privacy, Apple believes you might possibly want to pay with Apple Pay & Safari gives you the button to do so.
Last I worked with Google Pay that “isreadytopay” thing always returned true, just like what you’ve observed. In this case, some rebrand/reorg blended Android Pay, Google Pay (web), and Google Wallet into some kind of heinous web hybrid called (creatively) Google Pay. Google Pay always believes it can work on any browser / settings combo because it’s part of the borg, and optimistic assimilation strategies prevail in their tech.
You know, you could read back to me the party line of what Apple says "should" happen, or you could actually try it, as I did before making this post. Because this is what it does when there is no card in your Apple Wallet at all, with canMakePaymentsWithActiveCard still returning true. "Active card" my ass.

Well, damn, I see the same on a Mac after removing all cards, regardless of the Safari privacy setting. There was a good 4–5 year stretch where canMakePaymentsWithActiveCard() actually worked as described, so it seems my cheese has moved. The old logic was "never make people set up Apple Pay during checkout to prevent abandonment," which was sensible & what merchants actually want.
Now their HIG says "Offer Apple Pay on all devices that support it" and "Use Apple Pay buttons only to trigger the Apple Pay payment process and, when appropriate, the Apple Pay set up process" which is the opposite of the old behavior. Rats.
This isn't surprising. Get everyone to hook Apple Pay up to their websites implementing that logic, then break the logic which has the side effect of the button always showing up seems like exactly how to get more people using Apple Pay with or without the consent of the websites hosting it.
And Google started with the switch, bypassing the bait entirely.
I wonder if `goog.isReadyToPay` is implemented as a simple `return true;` or whether it's more... Enterprise-y?
The function that I think it is unminifies to ~50 lines of JS, and appears to do something with promises. It's hard for me to say more, because the unminifier I found didn't try to un-obfuscate the function names, and I'm insufficiently curious about whether it's possible to get it to
return false
to track down all of the mono- and di- graph function names it's using.