Plaidly Docs

WooCommerce Integration

Accept crypto payments on WooCommerce with the Plaidly payment gateway plugin

WooCommerce Integration

Integrate Plaidly into your WooCommerce store using a custom payment gateway plugin. This guide walks through building a minimal gateway plugin against the live POST /v1/payment_sessions endpoint.

Prerequisites

  • WordPress + WooCommerce installed
  • PHP 8.1+
  • A Plaidly API key and webhook secret — see Register a merchant
  • SSL/HTTPS on your store (required by WooCommerce for payment gateways)

1. Create the plugin

Create a file at wp-content/plugins/plaidly-woocommerce/plaidly-woocommerce.php:

<?php
/**
 * Plugin Name: Plaidly Crypto Payments
 * Description: Accept cryptocurrency payments via Plaidly
 * Version: 1.0.0
 * Requires Plugins: woocommerce
 */

if ( ! defined( 'ABSPATH' ) ) exit;

add_filter( 'woocommerce_payment_gateways', function( $gateways ) {
    $gateways[] = 'WC_Plaidly_Gateway';
    return $gateways;
} );

add_action( 'plugins_loaded', function() {
    if ( ! class_exists( 'WC_Payment_Gateway' ) ) return;

    class WC_Plaidly_Gateway extends WC_Payment_Gateway {

        public function __construct() {
            $this->id                 = 'plaidly';
            $this->method_title       = 'Crypto (Plaidly)';
            $this->method_description = 'Accept USDC, USDT, and other tokens via Plaidly';
            $this->has_fields         = false;
            $this->supports           = [ 'products' ];

            $this->init_form_fields();
            $this->init_settings();

            $this->title       = $this->get_option( 'title' );
            $this->description = $this->get_option( 'description' );
            $this->api_key     = $this->get_option( 'api_key' );
            $this->webhook_secret = $this->get_option( 'webhook_secret' );
            $this->chain       = $this->get_option( 'chain', 'solana' );
            $this->currency    = $this->get_option( 'currency', 'USDC' );
            $this->network     = $this->get_option( 'network', 'mainnet' );

            add_action( 'woocommerce_update_options_payment_gateways_' . $this->id,
                [ $this, 'process_admin_options' ] );
        }

        public function init_form_fields() {
            $this->form_fields = [
                'enabled'        => [
                    'title'   => 'Enable',
                    'type'    => 'checkbox',
                    'label'   => 'Enable Plaidly Crypto Payments',
                    'default' => 'yes',
                ],
                'title'          => [
                    'title'   => 'Title',
                    'type'    => 'text',
                    'default' => 'Pay with Crypto',
                ],
                'description'    => [
                    'title'   => 'Description',
                    'type'    => 'text',
                    'default' => 'Pay with USDC, USDT, or other stablecoins',
                ],
                'api_key'        => [
                    'title' => 'Plaidly API Key',
                    'type'  => 'password',
                ],
                'webhook_secret' => [
                    'title' => 'Webhook Secret',
                    'type'  => 'password',
                ],
                'chain'          => [
                    'title'   => 'Chain',
                    'type'    => 'select',
                    'options' => [
                        'solana'   => 'Solana',
                        'ethereum' => 'Ethereum',
                        'polygon'  => 'Polygon',
                        'bsc'      => 'BNB Smart Chain',
                        'tron'     => 'Tron',
                        'ton'      => 'TON',
                    ],
                    'default' => 'solana',
                ],
                'currency'       => [
                    'title'   => 'Token',
                    'type'    => 'select',
                    'options' => [ 'USDC' => 'USDC', 'USDT' => 'USDT' ],
                    'default' => 'USDC',
                ],
            ];
        }

        public function process_payment( $order_id ) {
            $order  = wc_get_order( $order_id );
            $amount = (float) number_format( (float) $order->get_total(), 2, '.', '' );

            // Live endpoint: POST /v1/payment_sessions (verified against
            // plaidly-api/api/openapi.yaml). Note the nested `paymentMethod`
            // object -- this is NOT the older flat chain/currency shape.
            $response = wp_remote_post( 'https://api.plaidly.io/v1/payment_sessions', [
                'headers' => [
                    'X-API-Key'        => $this->api_key,
                    'Content-Type'     => 'application/json',
                    'Idempotency-Key'  => 'wc-order-' . $order_id,
                ],
                'body' => wp_json_encode( [
                    'amount'      => $amount,
                    'expires_in'  => '30m',
                    'paymentMethod' => [
                        'methodID' => 0,
                        'chain'    => $this->chain,
                        'token'    => $this->currency,
                        'network'  => $this->network,
                    ],
                    'metadata'    => [
                        'order_id'  => (string) $order_id,
                        'order_key' => $order->get_order_key(),
                    ],
                ] ),
            ] );

            if ( is_wp_error( $response ) ) {
                wc_add_notice( 'Payment error: unable to reach payment processor', 'error' );
                return [ 'result' => 'failure' ];
            }

            $session = json_decode( wp_remote_retrieve_body( $response ), true );

            if ( ! $session || ! isset( $session['session_id'] ) ) {
                wc_add_notice( 'Payment error: invalid response from payment processor', 'error' );
                return [ 'result' => 'failure' ];
            }

            // Store session ID on the order
            $order->update_meta_data( '_plaidly_session_id', $session['session_id'] );
            $order->update_status( 'pending', 'Awaiting crypto payment via Plaidly' );
            $order->save();

            return [
                'result'   => 'success',
                'redirect' => $session['payment_url'] ??
                    add_query_arg( 'plaidly_session', $session['session_id'],
                        wc_get_checkout_url() ),
            ];
        }
    }
} );

2. Register the webhook endpoint

Add to your plugin file. The signature scheme is t=<unix_seconds>,v1=<hex> where v1 is HMAC-SHA256(webhook_secret, "<t>.<raw_body>") — see Verify a webhook event for the canonical algorithm this mirrors:

add_action( 'rest_api_init', function() {
    register_rest_route( 'plaidly/v1', '/webhook', [
        'methods'             => 'POST',
        'callback'            => 'plaidly_handle_webhook',
        'permission_callback' => '__return_true',
    ] );
} );

function plaidly_handle_webhook_verify( string $raw_body, string $signature_header, string $secret, int $tolerance_seconds = 300 ): bool {
    $parts = [];
    foreach ( explode( ',', $signature_header ) as $part ) {
        [ $key, $value ] = array_pad( explode( '=', trim( $part ), 2 ), 2, null );
        $parts[ $key ] = $value;
    }

    if ( empty( $parts['t'] ) || empty( $parts['v1'] ) ) {
        return false;
    }

    if ( abs( time() - (int) $parts['t'] ) > $tolerance_seconds ) {
        return false;
    }

    $expected = hash_hmac( 'sha256', $parts['t'] . '.' . $raw_body, $secret );
    return hash_equals( $expected, $parts['v1'] );
}

function plaidly_handle_webhook( WP_REST_Request $request ) {
    $body      = $request->get_body();
    $signature = $request->get_header( 'x-plaidly-signature' );
    $secret    = get_option( 'woocommerce_plaidly_settings' )['webhook_secret'] ?? '';

    if ( ! plaidly_handle_webhook_verify( $body, $signature ?? '', $secret ) ) {
        return new WP_REST_Response( 'Unauthorized', 401 );
    }

    $event = json_decode( $body, true );

    if ( ( $event['event_type'] ?? '' ) === 'payment_session.completed' ) {
        $order_id = $event['metadata']['order_id'] ?? null;
        if ( $order_id ) {
            $order = wc_get_order( (int) $order_id );
            if ( $order && $order->has_status( 'pending' ) ) {
                $order->payment_complete( $event['session_id'] ?? '' );
                $order->add_order_note(
                    'Payment confirmed via Plaidly. Session: ' .
                    ( $event['session_id'] ?? 'N/A' )
                );
            }
        }
    }

    return new WP_REST_Response( 'OK', 200 );
}

3. Activate and configure

  1. Upload the plugin to wp-content/plugins/plaidly-woocommerce/
  2. Activate it in WordPress Admin → Plugins
  3. Go to WooCommerce → Settings → Payments → Crypto (Plaidly)
  4. Enter the API Key and Webhook Secret you got back from merchant registration — there is currently no dashboard to look these up after the fact; if you lost them, rotate credentials to issue new ones
  5. Select your preferred Chain and Token
  6. Enable the gateway and save

4. Set the webhook URL

webhook_url is set once, at merchant registration (POST /v1/merchants) — there is no dashboard or update endpoint to change it afterward today. Register your sandbox/live merchant with the final webhook URL already in place:

https://yourstore.com/wp-json/plaidly/v1/webhook

See Configure a webhook for the current limitation and workaround.

Testing

Set chain to solana, register a sandbox merchant ("sandbox": true at registration — the resulting api_key is prefixed plaidly_sk_test_), and use Simulate a payment to trigger a test confirmation without broadcasting a real transaction.

HPOS compatibility

The plugin is compatible with WooCommerce High-Performance Order Storage (HPOS). The update_meta_data and get_meta calls use WooCommerce's CRUD methods.

On this page