Dev.to WebDev 🛠 Dev 👁 0 📖 7 min read

Adding a crypto checkout to WooCommerce without signing up for a processor

Most guides to taking crypto in WooCommerce start the same way: create an account with a payment processor, upload your company documents, wait for a review, then install their plugin and paste in an API key. The plugin

Adding a crypto checkout to WooCommerce without signing up for a processor

Most guides to taking crypto in WooCommerce start the same way: create an account with a payment processor, upload your company documents, wait for a review, then install their plugin and paste in an API key. The plugin is the easy part. The account is what slows you down, and it's also the thing that can be closed later.

This walkthrough takes a different route. The plugin talks to a payment server you run yourself, so there's no account to apply for. I'll use Coldtill, the gateway we build, and its WooCommerce plugin. It's a short job, but there are a few details (store currency, webhook URLs, partial payments) that are worth knowing before a real customer finds them for you.

What you need

On the WordPress side:

  • WordPress 6.4 or later, WooCommerce 8.0 or later, PHP 8.1 or later
  • Store currency set to USD. Coldtill prices payments in US dollars, so the gateway simply doesn't show up at checkout if your store uses another currency. This is the most common "I installed it and nothing happened" moment.

On the payment side, a Coldtill server: a Linux box with Docker. 2 vCPU and 4 GB of memory is plenty, on amd64 or arm64. Put it on its own small VPS, not on the machine that runs WordPress. If a plugin vulnerability ever gets someone into your shop, it shouldn't hand them the payment server too.

1. Install the payment server

The Free edition installs with one command and doesn't need an account:

# Ubuntu or Debian
sudo apt install docker.io docker-compose-v2 curl
curl -fsSL https://coldtill.com/install.sh | sudo bash -s -- --edition free

The script pulls the latest release from the Coldtill/self-hosted-crypto-payment-gateway repository, checks its SHA-256 against the published checksums and starts the stack in /opt/coldtill. It installs on testnet by default, which is exactly what you want while wiring up the shop. When you're ready for real money, you install with --mode mainnet --site-url https://pay.yourshop.com.

Once it's up, the dashboard's Get started page walks you through adding a cold wallet and at least one deposit wallet. One habit worth forming here: when the dashboard shows you the first deposit address it derived, check it against your own wallet before you share a single payment link.

A quick sanity check from your laptop:

curl -s https://pay.yourshop.com/healthz

If that answers, the shop will be able to reach it too.

2. Install the plugin

The plugin is MIT-licensed and lives in its own repo. Grab coldtill-woocommerce.zip from the latest WooCommerce plugin release and upload it under Plugins → Add New → Upload Plugin, then activate it.

If you manage WordPress from the command line, WP-CLI can install straight from the release URL:

wp plugin install \
  https://github.com/Coldtill/woocommerce-crypto-payment-gateway/releases/latest/download/coldtill-woocommerce.zip \
  --activate

It supports both the classic checkout and the block-based checkout, and it's compatible with WooCommerce's high-performance order storage (HPOS), so you don't need to switch anything back to make it work.

3. Create an API key, and make it a payments-only one

In the Coldtill dashboard, open API keys and create a new key for the project. Coldtill keys come in two scopes:

  • Payments only: create payments and read their status.
  • Full access: everything above, plus sending payouts.

A shop plugin only needs the first. Pick payments-only here. The scope can't be changed after the key is created, so if you get it wrong, make a new key and revoke the old one. The key is shown once; paste it straight into WordPress.

In WooCommerce → Settings → Payments → Coldtill, enter your Coldtill URL (for example https://pay.yourshop.com) and the key.

4. Connect the webhook

The plugin settings show a webhook URL under "Webhook secret". It ends in /wc-api/coldtill/, something like:

https://shop.example.com/wc-api/coldtill/

Back in Coldtill, go to Webhooks, add an endpoint with that URL, copy the signing secret it gives you (also shown once) and paste it into the plugin settings.

The plugin verifies every webhook with HMAC-SHA256 over a timestamp and the body, and rejects anything with a bad signature or a timestamp outside the replay window. You don't have to write that code, but it's worth knowing it's there, because it explains one failure mode: if your WordPress server's clock is badly off, valid webhooks will be refused.

Two other things that bite here:

  • Redirects aren't followed. If http:// redirects to https://, or shop.example.com redirects to www.shop.example.com, the webhook won't arrive. Use the exact final URL.
  • On mainnet, the URL has to be public HTTPS. Coldtill refuses private and reserved addresses. On a testnet install you can point it at http://localhost while you develop.

5. What happens at checkout

When a customer chooses crypto, the plugin creates a payment on your server. It sends the order total in USD, the order number, the billing email, an internal customer id (the WordPress user id, or the order id for guests) and the order-received page address for the checkout's return button. Nothing goes to any other service.

The customer lands on your own checkout page, picks a network and coin, and gets a QR code and an exact amount.

The checkout page where the customer picks a network and currency
The customer picks a network and currency on your own checkout page. The invoice number comes from the WooCommerce order.

Because the customer id is stable, a returning logged-in customer gets the same deposit addresses as before. On Ethereum, Base, Polygon, BNB Chain, Tron and Solana those addresses are deposit contracts that can only send to your cold wallet; Bitcoin goes to addresses from your own wallet's public key.

6. How order statuses map

This is the part I'd read twice. Coldtill payments have a handful of states, and the plugin maps them onto WooCommerce like this:

Coldtill payment WooCommerce order
Filled or overpaid Processing (payment complete)
Partially paid On hold
Cancelled or replaced Cancelled

A few notes from the docs that matter in practice:

  • Filled means the confirmed amount is within 1% of what's owed. Overpaid means more than 1% above it. Both mean "ship it". If someone overpays, the plugin notes it on the order; you can refund the difference with a payout if you want to.
  • Partially paid usually means the customer sent too little, often because their exchange took a withdrawal fee off the top. The checkout page asks them for the rest, and the order sits On hold until they send it.
  • Cancelled happens when a payment expires unpaid. The default expiry is 24 hours.
  • Replaced means the same customer started a new payment, which supersedes the old one.

There's also a safety check: the order only changes when the payment's reference and amount match the order. If the amount Coldtill reports doesn't match the order total, the plugin adds a note and leaves the order alone rather than guessing. (Version 1.0.2 fixed a case where a payment exactly one cent off the total was left pending; it's now marked paid as intended.)

The Payments page in the Coldtill dashboard
The Payments page in the Coldtill dashboard. Each row lines up with a WooCommerce order through its invoice number, so a "partially filled" here is an "On hold" in WooCommerce.

7. When a webhook goes missing

Webhooks fail for boring reasons: a deploy, a firewall rule, a security plugin that dislikes POST requests. Coldtill retries final events on a long schedule (up to two days), and you can resend any delivery by hand under Webhooks → Deliveries.

The plugin also has a fallback: when the customer comes back to the order-received page, it asks the server for that payment's status, so the order still updates even if the webhook never arrived.

If you're debugging an order that looks stuck, you can ask the server directly with the payment's reference id. It's the last part of the checkout link (/pay/<reference_id>), which the dashboard's Payments page links to:

curl -s \
  -H "API-Key: $COLDTILL_API_KEY" \
  https://pay.yourshop.com/api/v1/payment/reference/fd09c7e5-d661-4355-9b04-7b3749b41e2e

The response includes the status and the deposits with their confirmation counts, which usually tells you straight away whether the problem is the chain (still confirming) or the shop (webhook not getting through).

8. Test before you go live

My usual order:

  1. On the testnet install, use Webhooks → Send test to post a signed test event to the shop and confirm it's accepted.
  2. Place a real order on the store and pay it with Base Sepolia USDC or Sepolia ETH from a faucet. Watch the order go from Pending to Processing.
  3. Pay too little on a second order and check it lands On hold.
  4. Let a third one expire, or create a payment with a short expiry through the API, and check it ends up Cancelled.
  5. Reinstall on mainnet, point the plugin at it, and pay yourself $1 before you announce anything.

That last step catches more than you'd think: DNS, certificates, a firewall that only lets your office in, a webhook URL with a stray www.

Wrapping up

The plugin itself is small. Most of the work is in the server it talks to and in understanding the order states, and once those are clear there isn't much to maintain. If you'd rather read the code before installing anything on a production store, the plugin is in the woocommerce-crypto-payment-gateway repo, and the full setup docs are at coldtill.com/docs.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.