Select Accepting Crypto Without Breaking Your Payment Stack Accepting Crypto Without Breaking Your Payment Stack

Accepting Crypto Without Breaking Your Payment Stack

Parsing Out Who Actually Needs Crypto Payments

Look, if you’re an indie SaaS selling to early adopters or devs in Eastern Europe, sure — crypto payments make sense. But for B2B software with annual contracts? You probably don’t need to let someone pay in DOGE. That said, we’ve been there — a client asked to pay in Ethereum during peak gas fees (classic), and we spent a full Saturday figuring out if it was even legal in our jurisdiction. Spoiler: mostly yes, depending on how you report it.

The practical line is: if you’re selling digital goods under $500, globally, and your existing customers are already tech-savvy, it’s worth offering crypto alongside Stripe/PayPal. Otherwise, wait until someone pushes hard for it. A surprising number of buzz-requesters have no intention of paying in it — they just like the optics. One guy asked if we took Solana “for the vibe.” Bro.

How Payment Processors Handle Crypto (and How They Fail)

Coinbase Commerce, BitPay, and OpenNode all claim to make this seamless. They mostly do… up until refunds or fraud come into play. Coinbase’s API actually triggers a successful webhook callback before the transaction is fully confirmed, which burned us once when we released a license key too early. Classic 0-conf problem. Internally, many of these platforms don’t tell you what happens if the user underpays due to fee estimation errors. They just categorize it as “Expired.” Ever try explaining that to a customer who paid $202 instead of $204?

There’s also an undocumented behavior with BitPay’s conversions: if you choose auto-convert to fiat, they batch conversions every 10–20 minutes. So if you’re pricing a product dynamically, someone might end up with a discount (or a surprise extra charge) unless you freeze fiat pricing per session.

Refunding? You’ll have fun requesting the user’s wallet address, triple checking they didn’t give you an exchange address, and manually pushing the transaction through Metamask or a CLI wallet while praying you goofed the gas calculation by less than an order of magnitude.

Managing Volatility Without Rigging a Pricing Oracle

Big question: do you price in crypto or fiat? Short answer: always price in fiat, convert at the point of sale. Lock the rate for 15 minutes or so. Don’t try to dynamically price in ETH — unless you want to recreate every DeFi NFT auction bug from 2021. I once took 0.04 BTC for a $200 course… and then BTC dropped like a brick an hour later. “Oh cool, I just sold this for $126.”

BitPay and Coinbase Commerce both let you denominate products in fiat and auto-calculate crypto totals. But — insider tip — they use slightly padded rates so they don’t eat slippage. This means if your customer is watching coinmarketcap.com or has a wallet plugin quoting real-time conversion, they’ll get suspicious if you’re overcharging them by 2–3%. Not malicious, just annoying.

Handling Receipts, Audit Trails, and Accounting from Hell

You haven’t lived until your accountant asks, “So this $317.88 from Ethereum — where did that live before you converted it to fiat?” And your only answer is: “Uh, somewhere in a MetaMask wallet called burner2.eth…?”

Stuff that will save you later:

  • Log the user’s transaction hash along with their order metadata.
  • Save the quoted fiat amount + base coin amount separately.
  • Grab a snapshot of your vendor’s exchange rate at time of transaction.
  • Don’t assume the wallet used to pay is the same one that wants a refund.
  • Give your accountant access to a read-only Etherscan bookmark folder. Let them cry in private.

Also: OpenNode doesn’t expose historical payouts via the web UI after 90 days. If your crypto accounting tool relies on that data (ours did), you’re in for a bad quarter.

This Is What the Integration Actually Looks Like

Most people expect some magical Stripe-like SDK, but even Coinbase Commerce’s drop-in is barely usable without heavy UI work. We ended up wrapping it in a modal + iframe, and manually triggering timeouts on the client to warn of price slippage after 10 minutes. Pro tip: if you integrate with their REST API, don’t trust the initial webhook to mean it’s paid. Wait for a CONFIRMED status or minimum X block confirmations.

{
  "event": "charge:confirmed",
  "data": {
    "metadata": {
      "order_id": "2137-A",
    }
  }
}

When we saw charge:created fire before any funds hit the address, that led to an automated email trigger (…oops) and a user expecting their download link immediately. Took me a day to rewrite the logic to wait-for-confirmed with exponential backoff polling.

Oh, and WordPress plugins for crypto checkout are almost universally sketchy. I’ve yet to find one that doesn’t either:

  • Break with minor WP updates
  • Handle wallet timeouts poorly
  • Exfiltrate sensitive data if you leave debug mode on

KYC, Sanctions, and the Silent Geoblock

Here’s the fun part: you’re thinking “cool, no banks, no rules,” but your payment processor still does KYC on their end. OpenNode won’t let you complete onboarding if you’re personally from a high-risk country, even if your company’s fine. We had a Ukrainian contractor blocked from payout for weeks because the address verification was based on a driver’s license watermark they couldn’t translate.

Coinbase Commerce restricts service in certain countries but doesn’t block users until transaction time. That means someone from Iran can go through the whole checkout process, generate a wallet, and then just see a generic “Transaction failed” error. We didn’t know for months.

The real kicker? Refunds to sanctioned regions might not just be blocked — they can get your entire merchant account frozen while they “review it.” One of our test refunds to a Turkish IP address triggered CoinPayments support to suspend our outgoing ETH wallet for 10 calendar days. I still don’t fully understand why — they never answered.

What Do Customers Actually Expect?

They expect three things: speed, anonymity (even if it’s pretend-anonymous), and instant confirmation. The reality is you’re giving them none of that:

  • Speed? You’re waiting 6–12 confirmations if you want safety.
  • Anonymity? You’ve got IP logs, wallet fingerprints, and a support thread.
  • Instant confirmation? Unless you’re hardcore, you’ll delay fulfillment until confirmation.

One user wrote: “If I wanted to wait 30 minutes for a payment to confirm, I’d use a bank wire.” That line stuck with me. We switched to a two-phase model after that. First email: “We received your transaction, awaiting confirmation.” Second email: fulfillment link. Customers were calmer when we just told the truth up front rather than pretending crypto is magic.

The Worst Part: Support Tickets

If they paid with crypto and something glitches? It’s never fixable in one reply. You ask for the transaction hash, they send a screenshot of a QR code. You ask how much they sent, they say “what you asked.” You ask what wallet they used, they respond with “my phone.” It’s like support roleplay but cursed.

We built a canned response policy after getting 6 tickets in a row where the user didn’t remember which coin they sent, or when. I now include a note at checkout saying: “Please save your transaction hash and this order ID. We will ask for both if anything goes wrong.” Honestly helped shave at least an hour off weekly support load.

Also, be real: don’t try to reverse a confirmed on-chain transaction. Even if someone fat-fingers the recipient address, it’s gone. We once had a guy literally send Litecoin to our old Dogecoin wallet from 2020. He’s still mad about that.

Browser and Extension Chaos With Wallets

You’d think connecting MetaMask was the hard part. Turns out, no — the problem is users with 7 adblockers, 2 pop-up blockers, and Brave Shields on max. These scripts will not run. We had a winrate increase of 30-something percent just by soft-disabling browser shields during checkout and warning users if window.ethereum wasn’t detected.

Also: some wallets inject window.web3, others use window.ethereum or nothing at all if the extension loads slowly (mobile especially). If you’re polling for presence, add retries with progressive backoff. Otherwise, you’ll get that delightful bug where Snap Wallet loads late and your buyer sees “Wallet not detected.” Especially on Firefox with uBlock or built-in tracking protection cranked up.

I had to use navigator.userAgent spoof detection at one point just to warn Samsung browser users to give up entirely. That thing fails at life.

Stupid Little Things That Will Break Your Checkout

There’s no structured list of all the ways a crypto transaction can half-fail. But here are the dumbest, most time-wasting issues we hit:

  • If the user sends less than required because of fee miscalculation, some processors will never mark it paid. You may never even be notified.
  • Lightning network transactions that route through >4 hops sometimes fail silently.
  • Safari private mode disables IndexedDB, so some wallet integrations just implode.
  • DNS-over-HTTPS on Firefox can block requests to cloud wallet endpoints (seen this personally when I forgot ODoH was on).
  • Charge sessions that expire mid-payment will sometimes auto-refresh on the user’s browser without notifying your backend webhook for failure.
  • Localization: Arabic numerals break some third-party plugins doing naive parseFloat() on amount fields. We had one misfire a $0 transaction because of numeral conversion.

The ugly truth with crypto checkout is not that it breaks — everything breaks — but that it often fails silently. PayPal at least yells at you in logs. This? It just shrugs.

Off-Platform? You’re On Your Own

Thinking of accepting direct wallet payments (“Send ETH to this address and email us a screenshot!”)? Cool idea in theory. But without an invoice/timestamp, you will lose track. We tried it for a limited launch and spent half the next week going:

“Is this the guy who paid 0.0193 ETH on Tuesday at noon, or the guy who paid 0.0201 ETH on Wednesday at 2am… or both?”

Unless you’re storing per-user order IDs + wallet address + fiat quoted equivalent + TX hash, you’re just running a donation box you can’t refund.

One workaround: we added a unique memo string to the payment instruction (like “Include ORDER-48AJ as message”). But wallets like MetaMask and Trust Wallet disable custom memos for base coin transfers. So users just ignored it. Excellent.

Similar Posts