The plugin registers an EDD payment gateway with the ID paddle. It supports two Paddle platforms through interchangeable drivers.
| Paddle Billing | Paddle Classic | |
|---|---|---|
| API | api.paddle.com / sandbox-api.paddle.com (REST, JSON, Authorization: Bearer) | 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 | 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
- EDD is the source of truth for prices. Nothing has to exist in the Paddle catalog; Paddle charges exactly the EDD order total.
- Nothing from the browser or a webhook is trusted without verification. Orders are completed 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 remember the API and mode they were created with, so refunds and subscription changes keep working after a store switches account type or goes live.
Requirements and compatibility
- PHP 7.4+ (no PHP 8-only syntax). The OpenSSL extension is required for Paddle Classic signature checks.
- WordPress 6.0+; Easy Digital Downloads 3.1+ (uses the EDD 3 order, refund and transaction APIs).
- Optional: EDD Recurring Payments (tested 2.13.13), EDD Software Licensing (tested 3.8.14).
- MySQL/MariaDB
GET_LOCK()(MySQL 5.7+ allows nested locks). - The plugin header sets
Update URI, so WordPress.org never offers an “update” for this plugin. There is intentionally noRequires Pluginsheader, so activation also works with EDD Pro.
Files
wpinterface-paddle-for-edd-pro/
├── wpinterface-paddle-for-edd-pro.php Bootstrap: constants, requires, boots Plugin on plugins_loaded:20
├── uninstall.php Opt-in removal of settings and caches
├── includes/
│ ├── class-plugin.php Service container, requirements, logging, API factories
│ ├── class-settings.php Settings section, credential lookup, panels
│ ├── class-gateway.php Gateway registration, one-time purchases, checkout AJAX, success page
│ ├── class-recurring-gateway.php EDD_Recurring_Gateway subclass (loaded only with EDD Recurring)
│ ├── class-orders.php Shared bookkeeping: completion, refunds, renewals, locks, ledger
│ ├── class-webhooks.php REST endpoint, signature dispatch
│ ├── class-refunds.php Admin refunds sent to Paddle
│ ├── class-customer.php Invoice/receipt link, update-payment-method URL
│ ├── class-admin.php Order panel, links, notices, AJAX tools, pause/resume
│ ├── class-signature.php Billing HMAC and Classic RSA verification
│ ├── api/ API_Exception, Billing_API, Classic_API
│ └── drivers/ Driver (abstract), Billing_Driver, Classic_Driver
├── assets/ paddle-checkout.js, admin.js, checkout.css, payment marks
└── languages/ Translation templateNamespace: WPInterface\Paddle_EDD_Pro (drivers in \Drivers, API clients in \API). Constants: WPINTERFACE_PADDLE_PRO_VERSION, WPINTERFACE_PADDLE_PRO_FILE, WPINTERFACE_PADDLE_PRO_DIR, WPINTERFACE_PADDLE_PRO_URL.
Boot sequence
| When | What |
|---|---|
| Plugin file load | All classes are required (no autoloader). |
plugins_loaded priority 20 | If EDD() exists, Plugin::instance() is created. Otherwise an admin notice is shown. |
Plugin::__construct | Creates Settings. If the requirements pass (EDD 3.1 functions; OpenSSL for Classic), creates Gateway, Webhooks, Refunds, Customer and, in admin, Admin. |
init | Text domain loaded (wpinterface-paddle-for-edd-pro). |
plugins_loaded priority 99 | If EDD_Recurring_Gateway exists (EDD Recurring boots at 98), Recurring_Gateway is instantiated and returned from the edd_recurring_gateway filter for the ID paddle. |
One Recurring_Gateway instance
Plugin::instance()->recurring must be the only Recurring_Gateway instance. EDD Recurring’s own get_gateway() would otherwise construct new instances, each registering its hooks again. That is why the filter returns the singleton and paddle is not added to EDD Recurring’s gateway map.
Drivers
Plugin::driver( $order = null ) returns the driver for an existing order (by its _wpinterface_paddle_api meta) or, for new orders, the configured API (Settings::api_mode()). Orders without the meta are treated as Classic. Plugin::driver_for_api( 'billing'|'classic' ) returns a specific driver.
| Method | Purpose |
|---|---|
api() | 'billing' or 'classic' |
start_checkout( $order, $purchase_data, $subscription ) | Create the Paddle transaction or pay link. Returns transaction_id or pay_link, plus redirect_url. |
refund( $order, $amount, $full, $reason, $refund = null ) | Refund in Paddle (amount in EDD terms). |
cancel_subscription( $subscription, $immediately ) | Cancel in Paddle; “already cancelled” counts as success. |
set_paused( $subscription, $paused ) | Pause at period end, or resume. |
next_billing_date( $subscription ) | Site-local next bill date (used by EDD Recurring’s expiration check). |
test_connection( $sandbox ) | Credential check with readable errors. |
transaction_url(), subscription_url() | Paddle dashboard links. |
sync_order( $order ) | Billing only: read the transaction from the API and complete the order if paid. |
one_time_lines() | Shared: turns the EDD cart into charge lines and the 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 log every call and are created through Plugin::billing_api( $sandbox ) / Plugin::classic_api( $sandbox ), which pick the credentials of the requested environment.