The plugin registers a WooCommerce payment gateway with the ID wpinterface_paddle (Plugin::GATEWAY_ID). It supports two Paddle platforms through interchangeable drivers.
| Paddle Billing | Paddle Classic | |
|---|---|---|
| API | api.paddle.com / sandbox-api.paddle.com (REST, JSON, Authorization: Bearer, Paddle-Version: 1) | vendors.paddle.com/api/2.0 / sandbox-vendors.paddle.com/api/2.0 (form POST, vendor_id + vendor_auth_code) |
| Browser library | Paddle.js v2 (Paddle.Initialize, Paddle.Checkout.open({ transactionId })) | Paddle.js v1 (Paddle.Setup, Paddle.Checkout.open({ override })) |
| What an order becomes | A transaction created server-side with non-catalog items | A pay link created by product/generate_pay_link |
| Subscriptions | A recurring non-catalog price in the transaction; Paddle creates the subscription | An auto-created Classic plan plus price overrides on the pay link |
| Webhook authentication | Paddle-Signature HMAC-SHA256 | p_signature RSA-SHA1 |
Design principles
- WooCommerce is the source of truth for prices. Nothing has to exist in the Paddle catalog; Paddle charges exactly the WooCommerce order total (plus any tax Paddle adds itself).
- Nothing from the browser or a webhook is trusted without verification. Orders are marked paid only by signed webhooks or by server-side API reads.
- Webhooks are processed idempotently and safely under concurrency. Paddle sends related events in parallel and retries freely.
- Every site on a Paddle account receives every webhook. Each site acts only on its own orders.
- Orders and subscriptions remember the API and environment they were created with, so refunds and subscription changes keep working after a store switches account type or goes live.
- Paddle collects renewals. WooCommerce Subscriptions never charges a Paddle subscription itself; each Paddle payment is recorded as a renewal order.
Requirements and compatibility
- PHP 7.4+ (no PHP 8-only syntax). The OpenSSL extension is needed for Paddle Classic webhook signatures (
Plugin::requirements_message()reports it on the settings screen). - WordPress 6.0+ (tested up to 7.1). WooCommerce 8.0+ (tested up to 11.2); the header declares
Requires Plugins: woocommerce. - HPOS and blocks: on
before_woocommerce_initthe main file declares compatibility forcustom_order_tablesandcart_checkout_blocks. All order access goes throughwc_get_order(),wc_get_orders()and the order meta APIs. - WooCommerce Subscriptions is optional. Without it the gateway is a one-time payment gateway.
- MySQL/MariaDB
GET_LOCK()with re-entrant named locks (MySQL 5.7+, MariaDB 10.0.2+). - Action Scheduler (bundled with WooCommerce) for the delayed subscription link check.
- The header sets
Update URI, so WordPress.org never offers an update for this slug; updates come from the WPInterface store.
Files
wpinterface-paddle-for-woocommerce-pro/
├── wpinterface-paddle-for-woocommerce-pro.php Bootstrap: constants, HPOS/blocks declarations, activation hook
├── uninstall.php Opt-in removal of settings, options, transients
├── includes/
│ ├── class-plugin.php Service container, gateway/blocks registration, drivers, API factories, site tag, logging
│ ├── class-settings.php Gateway form fields and read helpers
│ ├── class-gateway.php WC_Payment_Gateway subclass: availability, process_payment, process_refund, receipt page
│ ├── class-blocks.php Payment method integration for the Cart & Checkout blocks
│ ├── class-checkout.php Front-end scripts, order status AJAX, confirmation page while unpaid
│ ├── class-orders.php Shared bookkeeping: completion, refunds, ledger, locks, fresh reads, lookups
│ ├── class-webhooks.php REST endpoint, signature dispatch, Billing event dedupe
│ ├── class-signature.php Billing HMAC and Classic RSA verification
│ ├── class-refunds.php Remembers "Refund manually" refunds so Paddle's report is matched
│ ├── class-subscriptions.php WooCommerce Subscriptions bridge
│ ├── class-customer.php Paddle invoice/receipt links on order pages and emails
│ ├── class-admin.php Order/subscription meta boxes, admin AJAX, setup notices
│ ├── class-admin-page.php WooCommerce → Paddle page
│ ├── class-license.php License activation/check and updater setup
│ ├── class-lite-upgrade.php Takeover from the free plugin
│ ├── api/ API_Exception, Billing_API, Classic_API
│ ├── drivers/ Driver (abstract), Billing_Driver, Classic_Driver
│ └── libraries/ EDD_SL_Plugin_Updater (bundled, v1.9.4)
├── assets/ paddle-core.js, paddle-checkout.js, blocks.js, admin scripts, CSS, payment marks
└── languages/ Translation templateBoot sequence
| When | What |
|---|---|
| Plugin file load | Defines constants, hooks before_woocommerce_init, requires class-lite-upgrade.php and registers Lite_Upgrade::on_activation. Nothing else is loaded yet. |
plugins_loaded priority 20 | If WooCommerce or WC_Payment_Gateway is missing, an admin notice is shown and nothing loads. Otherwise every class file is required (no autoloader) and Plugin::instance() is created. |
Plugin::__construct | Adds the gateway, registers the blocks integration, hooks Settings::flush to the settings save, instantiates Checkout, Webhooks, Refunds, Subscriptions, Customer, License, and in admin Admin, Admin_Page, Lite_Upgrade; then fires wpinterface_paddle_woo_loaded. |
init | Text domain loaded; License::updater() creates the updater when a key is saved. |
| On demand | WooCommerce instantiates Gateway when it builds its gateway list. Use Plugin::gateway() to get that instance. |
The plugin doesn’t refuse to boot when a requirement is missing: Plugin::requirements_ok() and requirements_message() only feed the status row on the settings screen.
Constants and namespaces
Namespace WPInterface\Paddle_Woo_Pro; drivers in \Drivers, API clients in \API.
| Constant | Value | Override in wp-config.php |
|---|---|---|
WPINTERFACE_PADDLE_WOO_PRO_VERSION | 1.0.0 | No |
WPINTERFACE_PADDLE_WOO_PRO_FILE, _DIR, _URL | Main file, its directory path and URL | No |
WPINTERFACE_PADDLE_WOO_PRO_STORE_URL | https://wpinterface.com/ | Yes |
WPINTERFACE_PADDLE_WOO_PRO_ITEM_ID | 71568 | Yes |
WPINTERFACE_PADDLE_WOO_PRO_ITEM_NAME | WPInterface Checkout with Paddle for WooCommerce Pro | Yes |
WPINTERFACE_PADDLE_WOO_PRO_ACCOUNT_URL, _SUPPORT_URL, _DOCS_URL | wpinterface.com pages | Yes |
The free plugin checks WPINTERFACE_PADDLE_WOO_PRO_VERSION and doesn’t boot when it is defined.
Drivers
Plugin::instance()->driver( $order = null ) returns the driver for an existing order (from _wpinterface_paddle_api; a missing value means Billing) or, without an order, the configured API. driver_for_api( 'billing' ) or ( 'classic' ) returns a specific driver; instances are cached per request. Plugin::order_sandbox( $order ) reads _wpinterface_paddle_sandbox.
| Method | Purpose |
|---|---|
api() | 'billing' or 'classic' |
start_checkout( $order ) | Create the Paddle transaction or pay link. Returns transaction_id (Billing) or pay_link (Classic). Throws on refusal. |
refund( $order, $amount, $full, $reason, $refund = null ) | Refund in Paddle. $amount is in WooCommerce terms; $refund is the WC_Order_Refund. |
cancel_subscription( $subscription, $immediately ) | Cancel in Paddle; “already cancelled” counts as success. |
set_paused( $subscription, $paused ) | Pause at period end, or resume. |
update_payment_method_url( $subscription ) | Paddle page where the customer updates the card, or ''. |
test_connection( $sandbox ) | Credential check with readable errors. |
invoice_url( $order ) | Default: the stored receipt URL. Billing: a fresh PDF URL from the API. |
sync_order( $order ) | Billing only: read the transaction and complete the order if paid. |
order_lines( $order, $subscription = null ) | Protected, shared: the order as Paddle lines plus a reconciliation amount. |
API clients
API\Billing_API::request( $method, $path, $body = null, $query = array() ) returns the decoded data member, or throws API_Exception (HTTP status as the code, Paddle’s error.code via get_paddle_code()). API\Classic_API::request( $action, $fields ) returns the response member, or throws when success is false. Both use a 30-second timeout, log every call as routine traffic, and are created through Plugin::billing_api( $sandbox = null ) / Plugin::classic_api( $sandbox = null ).
Gateway
Gateway extends \WC_Payment_Gateway with has_fields = false and:
class-gateway.php
$this->supports = array(
'products',
'refunds',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'gateway_scheduled_payments',
);gateway_scheduled_payments tells WooCommerce Subscriptions that the gateway schedules and collects renewals itself. The plugin deliberately doesn’t declare subscription_amount_changes, subscription_date_changes, multiple_subscriptions or the payment-method-change features.
is_available() returns false unless the parent check passes, Settings::is_configured() is true, and the currency is supported. With Classic, it also hides the gateway when the cart or order contains a subscription and the currency is not USD, EUR or GBP.
Blocks integration
Blocks extends AbstractPaymentMethodType with $name = 'wpinterface_paddle'. is_active() means enabled and configured; per-cart availability is still decided by Gateway::is_available() through the Store API. get_payment_method_data() passes title, description, icons and supports, read in JavaScript as wcSettings.getSetting( 'wpinterface_paddle_data' ).