Architecture

Updated

The plugin registers a WooCommerce payment gateway with the ID wpinterface_paddle (Plugin::GATEWAY_ID). It supports two Paddle platforms through interchangeable drivers.

Paddle BillingPaddle Classic
APIapi.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 libraryPaddle.js v2 (Paddle.Initialize, Paddle.Checkout.open({ transactionId }))Paddle.js v1 (Paddle.Setup, Paddle.Checkout.open({ override }))
What an order becomesA transaction created server-side with non-catalog itemsA pay link created by product/generate_pay_link
SubscriptionsA recurring non-catalog price in the transaction; Paddle creates the subscriptionAn auto-created Classic plan plus price overrides on the pay link
Webhook authenticationPaddle-Signature HMAC-SHA256p_signature RSA-SHA1

Design principles

  1. 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).
  2. 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.
  3. Webhooks are processed idempotently and safely under concurrency. Paddle sends related events in parallel and retries freely.
  4. Every site on a Paddle account receives every webhook. Each site acts only on its own orders.
  5. 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.
  6. 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_init the main file declares compatibility for custom_order_tables and cart_checkout_blocks. All order access goes through wc_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 template

Boot sequence

WhenWhat
Plugin file loadDefines constants, hooks before_woocommerce_init, requires class-lite-upgrade.php and registers Lite_Upgrade::on_activation. Nothing else is loaded yet.
plugins_loaded priority 20If 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::__constructAdds 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.
initText domain loaded; License::updater() creates the updater when a key is saved.
On demandWooCommerce 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.

ConstantValueOverride in wp-config.php
WPINTERFACE_PADDLE_WOO_PRO_VERSION1.0.0No
WPINTERFACE_PADDLE_WOO_PRO_FILE, _DIR, _URLMain file, its directory path and URLNo
WPINTERFACE_PADDLE_WOO_PRO_STORE_URLhttps://wpinterface.com/Yes
WPINTERFACE_PADDLE_WOO_PRO_ITEM_ID71568Yes
WPINTERFACE_PADDLE_WOO_PRO_ITEM_NAMEWPInterface Checkout with Paddle for WooCommerce ProYes
WPINTERFACE_PADDLE_WOO_PRO_ACCOUNT_URL, _SUPPORT_URL, _DOCS_URLwpinterface.com pagesYes

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.

MethodPurpose
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' ).

Was this page helpful?