At Blockchain Centre NBO, we run an event ticketing platform built on WordPress Multisite and WooCommerce. When we decided to accept Cardano (ADA) as a payment method, we quickly discovered that existing plugins were either abandoned, over-engineered, or simply didn't work with the WooCommerce Blocks checkout.
So we built our own.
Here's how we did it — the architecture, the gotchas, and the lessons learned.
The Architecture
The plugin is structured around three core concerns:
- Price discovery — fetching live ADA/USD rates from multiple exchanges
- Checkout integration — supporting both classic and block-based WooCommerce checkout
- Wallet connection — browser-based Cardano wallet interaction on the thank-you page
Price Discovery: Redundant Exchange Rate Fetching
Crypto prices are volatile, and any single API can fail. We built a quote service using the Saloon HTTP library that fires concurrent requests to three sources:
$pool = $connector->pool([ 'coinapi' => new CoinApiGetQuoteRequest($base, $quote), 'coinbase' => new CoinbaseGetQuoteRequest($base, $quote), 'coinmarketcap' => new CoinmarketcapGetQuoteRequest(2010),]);$pool->withResponseHandler(function (Response $response, string $key) { match ($key) { 'coinapi' => $this->resolveCoinapi($response), 'coinbase' => $this->resolveCoinbase($response), 'coinmarketcap' => $this->resolveCoinmarketcap($response), };})->send()->wait();// Calculate the average from all successful responses$avgRate = array_sum($this->ratesArray) / count($this->ratesArray);
This gives us two advantages: resilience (if one API is down, the others still respond) and accuracy (averaging across sources smooths out anomalies). We cache prices via WordPress transients with a 1-hour TTL to avoid rate limits.
For the block-based checkout, we also fall back to CoinGecko's free API on the frontend:
$resp = wp_remote_get( 'https://api.coingecko.com/api/v3/simple/price?ids=cardano&vs_currencies=' . strtolower($currency));
WooCommerce Gateway: Classic + Blocks
WooCommerce has two checkout experiences — the classic PHP-rendered checkout and the React-based Blocks checkout. Supporting both means implementing two separate integration points.
Classic Checkout
The classic checkout extends the WC_Payment_Gateway abstract class:
class ADAPAYFWC_GateWay extends WC_Payment_Gateway{ public function __construct() { $this->id = 'ada_payment'; $this->title = __('Ada Payment', 'lidodevs-adapayments-for-woocommerce'); $this->method_description = __('Connect wallet and pay with ADA', 'lidodevs-adapayments-for-woocommerce'); $this->order_button_text = __('Sign Transaction', 'lidodevs-adapayments-for-woocommerce'); $this->description = $this->get_option('description', ''); // ← important: always initialize this // ... }}
Blocks Checkout
The Blocks checkout requires extending AbstractPaymentMethodType and registering a React component:
class ADAPAYFWC_Blocks extends AbstractPaymentMethodType{ protected $name = 'ada_payment'; public function get_payment_method_script_handles(): array { wp_register_script( 'ada_payment-blocks-integration', ADAPAYFWC_PLUGIN_URL . 'build/index.js', ['wp-element', 'wp-components', 'wp-data', 'wp-i18n'], $version, true ); wp_script_add_data('ada_payment-blocks-integration', 'type', 'module'); return ['ada_payment-blocks-integration']; }}
The block integration registers a React component that renders the payment option in the checkout sidebar, with pricing data passed to the frontend via wp_localize_script.
You also need to explicitly declare Blocks compatibility — without this, WooCommerce will refuse to load your payment method in the block checkout:
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true);
The Frontend: Wallet Connection and Transaction Building
The browser-side JavaScript bundle uses the Harmonic Labs ecosystem of Cardano libraries (@harmoniclabs/cbor, @harmoniclabs/bytestring, @harmoniclabs/plutus) to:
- Detect available Cardano wallets (Nami, Eternl, Lace, etc.) via the
window.cardanoAPI - Build a payment transaction with the correct lovelace amount
- Request wallet signature and submission
- Report the transaction hash back to WordPress via a custom REST endpoint
The ADA amount conversion happens in PHP before the page renders:
$ada_price = ADAPAYFWC_get_ada_price_in_currency($store_currency, $settings);$ada_amount = $ada_price > 0 ? $order_total / $ada_price : 0;$data = [ 'storeAddress' => $settings['walletAddress'] ?? null, 'adaLovelace' => (int) round($ada_amount * 1_000_000), // 1 ADA = 1,000,000 lovelace 'network_id' => str_contains($settings['network'], 'preview') ? 0 : 1,];
Note: Using
1_000_000(PHP 7.4+ numeric separator) instead of1000000makes the lovelace conversion immediately readable.
Once the user signs and submits the transaction in their wallet, the frontend POSTs the transaction hash to a custom REST endpoint:
register_rest_route('adapayments-woocommerce/v1', '/complete-order', [ 'methods' => 'POST', 'callback' => 'ADAPAYFWC_update_order_status', // ...]);
The endpoint validates the hash, updates the WooCommerce order status, and records the payment in a custom {$wpdb->prefix}ada_payments table.
Lessons Learned
1. PHP 8 Deprecation Warnings
The most common production error we encountered:
preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated
This came from WordPress's kses.php sanitization pipeline. The root cause: the gateway's $description property was never initialized in the constructor, so the Blocks integration passed null through get_payment_method_data().
The fix was a single line in the constructor:
$this->description = $this->get_option('description', '');
A good reminder that PHP 8's stricter type handling surfaces bugs that older versions silently ignored.
2. CoinAPI vs CoinGecko Rate Limits
We initially relied solely on CoinAPI for price data, but the free tier has tight rate limits. Adding CoinGecko as a fallback for the frontend was a pragmatic choice — it requires no API key and handles our volume without issue. The trade-off is slightly less precision, but for event ticketing it's more than adequate.
3. WooCommerce Blocks Compatibility Declaration
As shown above, the declare_compatibility() call is mandatory. We also had to handle the case where wc_blocks_is_checkout_page() doesn't exist on older WooCommerce versions — a simple function_exists() check resolved that.
4. Transaction Tracking
We created a custom {$wpdb->prefix}ada_payments table to store lovelace values, conversion rates, and transaction hashes. This lets us display each transaction's shortened hash with a link to CExplorer directly in the WooCommerce order list — crucial for reconciling on-chain payments with WooCommerce orders.
What's Next
The current implementation works well for our event ticketing use case, but there are areas we'd like to improve:
- Webhook-based payment confirmation — polling is functional, but a webhook from a block explorer would be more reliable
- Multi-currency support — right now we convert to ADA from the store currency; supporting direct ADA pricing would be cleaner
- Plutus script integration — implementing time-locked refunds for unpaid orders directly on-chain
The Code
The plugin is available in our repository. If you're running a WooCommerce store and want to accept Cardano, or if you're just curious about the integration, feel free to explore, fork, or contribute.
Built at Blockchain Centre NBO
¡Sé el primero en compartir tus pensamientos!