Building Plugins with coinsnap-core
coinsnap-core is a shared PHP library for building WordPress plugins that accept Bitcoin and Lightning payments. It handles the payment pipeline — API calls, webhook verification, settings pages, transaction tables, logging — so you can focus on what makes your plugin unique.
Use coinsnap-core when you are building a custom non-WooCommerce plugin (a donation form, paywall, crowdfunding widget, custom checkout, etc.). For WooCommerce, use the Coinsnap for WooCommerce plugin instead.
Requirements
- PHP 7.4+
- WordPress 6.0+
- A Coinsnap Store ID and API key
Installation
Bundle coinsnap-core inside your plugin's vendor/ directory. It is not installed separately.
# Git submodule (recommended)
cd your-plugin/
git submodule add https://github.com/coinsnap/coinsnap-core.git vendor/coinsnap-core
Or download and extract manually to your-plugin/vendor/coinsnap-core/.
Then load it from your main plugin file:
require_once plugin_dir_path( __FILE__ ) . 'vendor/coinsnap-core/coinsnap-core.php';
The library includes a double-load guard (COINSNAP_CORE_VERSION), so it's safe to bundle in multiple plugins running on the same site simultaneously.
Setup — PluginInstance
Every consuming plugin starts by creating a PluginInstance. This is an immutable config object that all core classes read from — it's how multiple Coinsnap-based plugins coexist on the same site without collisions.
use CoinsnapCore\PluginInstance;
$instance = new PluginInstance( array(
'plugin_name' => 'My Bitcoin Plugin',
'option_key' => 'my_plugin_settings', // wp_options key for plugin settings
'webhook_key' => 'my_plugin_webhook', // wp_options key for webhook data
'table_suffix' => 'my_plugin_payments', // appended to $wpdb->prefix
'rest_namespace' => 'myplugin/v1', // WordPress REST API namespace
'referral_code' => 'YOUR_REFERRAL_CODE',
'menu_slug' => 'my-plugin',
) );
Pass this instance into every core class call — never use globals or singletons.
Creating an Invoice
Use ProviderFactory::create() to get the active provider, then call create_invoice():
use CoinsnapCore\Util\ProviderFactory;
$provider = ProviderFactory::create( $instance );
$invoice = $provider->create_invoice(
$form_id = 42, // your internal source ID (form, post, campaign, etc.)
$amount = 1500, // amount in cents — 1500 = 15.00
$currency = 'EUR',
$data = array(
'email' => 'buyer@example.com',
'metadata' => array( 'order_id' => 99 ), // anything you need back in the webhook
)
);
if ( empty( $invoice ) ) {
// Invoice creation failed — API credentials may be missing or incorrect
}
// Redirect the customer to the Coinsnap payment page
wp_redirect( $invoice['payment_url'] );
// Save $invoice['invoice_id'] to match with the webhook later
Return value:
| Key | Type | Description |
|---|---|---|
invoice_id | string | Coinsnap invoice ID — save this to correlate with incoming webhooks |
payment_url | string | URL of the Coinsnap-hosted payment page — redirect the customer here |
Returns an empty array on failure (missing credentials or API error).
Supported currencies: EUR, USD, SATS, BTC, CAD, JPY, GBP, CHF, RUB
Handling Webhooks
Register a REST route in WordPress, then use WebhookHelper to verify the signature and parse the event:
use CoinsnapCore\Rest\WebhookHelper;
// Register the route
add_action( 'rest_api_init', function () use ( $instance ) {
register_rest_route( $instance->rest_namespace(), '/webhook/coinsnap', array(
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ( WP_REST_Request $request ) use ( $instance ) {
// 1. Verify signature
if ( ! WebhookHelper::verify_coinsnap_signature( $instance ) ) {
return new WP_Error( 'invalid_signature', 'Unauthorized', array( 'status' => 401 ) );
}
// 2. Parse the event
$data = json_decode( $request->get_body(), true ) ?: array();
$parsed = WebhookHelper::parse_webhook( 'coinsnap', $data );
// 3. Act on settlement
if ( $parsed['paid'] ) {
// Fulfill: grant access, email customer, update your DB, etc.
do_action( 'my_plugin_payment_settled', $parsed['invoice_id'], $parsed['metadata'] );
}
return rest_ensure_response( array( 'received' => true ) );
},
) );
} );
parse_webhook() return value:
| Key | Type | Description |
|---|---|---|
invoice_id | string | The Coinsnap invoice ID |
paid | bool | true when the event type is Settled (or equivalent) |
type | string | Raw event type from the webhook payload |
metadata | array | Full raw webhook payload |
The webhook URL registered with Coinsnap will be:
https://yoursite.com/wp-json/{rest_namespace}/webhook/coinsnap
Webhook registration happens automatically when an admin clicks the connection check on the settings page.
Admin Pages
Drop in the built-in settings page, transactions list, and log viewer with a few lines:
use CoinsnapCore\Admin\SettingsPage;
use CoinsnapCore\Admin\TransactionsPage;
use CoinsnapCore\Admin\AjaxHandlers;
add_action( 'admin_menu', function () use ( $instance ) {
add_menu_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
function () use ( $instance ) { TransactionsPage::render_page_for( $instance ); }
);
add_submenu_page(
'my-plugin', 'Settings', 'Settings', 'manage_options', 'my-plugin-settings',
function () use ( $instance ) { SettingsPage::render_page_for( $instance ); }
);
} );
add_action( 'admin_init', function () use ( $instance ) {
SettingsPage::register_for( $instance );
} );
// AJAX handler for the connection check button on the settings page
add_action( 'wp_ajax_my_plugin_connection_check', function () use ( $instance ) {
AjaxHandlers::handle_connection_check( $instance );
} );
The settings page includes fields for Store ID, API key, log level, and webhook verification toggle. It also renders a connection check button that verifies credentials and registers the webhook automatically.
Database Table
Call PaymentTable::activate() on plugin activation to create the payments table:
use CoinsnapCore\Database\PaymentTable;
register_activation_hook( __FILE__, function () use ( $instance ) {
PaymentTable::activate( $instance );
} );
This creates {$wpdb->prefix}{table_suffix} with columns for source_id, invoice_id, amount, currency, payment_status, customer_email, and timestamps. The Transactions admin page reads from this table automatically.
Hooks
| Hook | Type | Args | Fires when |
|---|---|---|---|
wpbn_coinsnap_response | action | $response, $form_id | After any Coinsnap API call |
wpbn_coinsnap_webhook_received | action | $request_body | When a webhook arrives, before parsing |
coinsnap_core_transaction_sources | filter | array $sources | Populate the source dropdown in the Transactions page |
coinsnap_core_transaction_source_label | filter | string $label, int $source_id | Resolve a source ID to a display label |
Example — wiring up a custom post type as the transaction source:
add_filter( 'coinsnap_core_transaction_sources', function ( $sources ) {
foreach ( get_posts( array( 'post_type' => 'my_campaign', 'posts_per_page' => -1 ) ) as $post ) {
$sources[] = array( 'id' => $post->ID, 'title' => $post->post_title );
}
return $sources;
} );
add_filter( 'coinsnap_core_transaction_source_label', function ( $label, $source_id ) {
$post = get_post( $source_id );
return $post ? $post->post_title : $label;
}, 10, 2 );
Minimal complete example
<?php
/**
* Plugin Name: My Bitcoin Plugin
* Requires PHP: 7.4
*/
require_once plugin_dir_path( __FILE__ ) . 'vendor/coinsnap-core/coinsnap-core.php';
use CoinsnapCore\PluginInstance;
use CoinsnapCore\Util\ProviderFactory;
use CoinsnapCore\Admin\SettingsPage;
use CoinsnapCore\Admin\AjaxHandlers;
use CoinsnapCore\Database\PaymentTable;
use CoinsnapCore\Rest\WebhookHelper;
add_action( 'plugins_loaded', function () {
$instance = new PluginInstance( array(
'plugin_name' => 'My Bitcoin Plugin',
'option_key' => 'mbp_settings',
'webhook_key' => 'mbp_webhook',
'table_suffix' => 'mbp_payments',
'rest_namespace' => 'mbp/v1',
'menu_slug' => 'mbp',
) );
register_activation_hook( __FILE__, fn() => PaymentTable::activate( $instance ) );
add_action( 'admin_init', fn() => SettingsPage::register_for( $instance ) );
add_action( 'wp_ajax_mbp_connection_check', fn() => AjaxHandlers::handle_connection_check( $instance ) );
add_action( 'rest_api_init', function () use ( $instance ) {
register_rest_route( $instance->rest_namespace(), '/webhook/coinsnap', array(
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ( WP_REST_Request $request ) use ( $instance ) {
if ( ! WebhookHelper::verify_coinsnap_signature( $instance ) ) {
return new WP_Error( 'unauthorized', 'Unauthorized', array( 'status' => 401 ) );
}
$parsed = WebhookHelper::parse_webhook( 'coinsnap', json_decode( $request->get_body(), true ) ?: array() );
if ( $parsed['paid'] ) {
// your fulfillment logic here
}
return rest_ensure_response( array( 'received' => true ) );
},
) );
} );
// Example shortcode that creates an invoice on form submit
add_shortcode( 'my_bitcoin_form', function () use ( $instance ) {
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
$provider = ProviderFactory::create( $instance );
$invoice = $provider->create_invoice( 0, 1000, 'EUR', array( 'email' => sanitize_email( $_POST['email'] ?? '' ) ) );
if ( ! empty( $invoice['payment_url'] ) ) {
wp_redirect( $invoice['payment_url'] );
exit;
}
}
return '<form method="post"><input name="email" type="email" required /><button type="submit">Pay €10</button></form>';
} );
} );