You've already forked wc-licensed-product
Fix license generation and checkout domain field bugs
- Add WooCommerce Checkout Blocks support for domain field - Create CheckoutBlocksIntegration for block-based checkout - Create StoreApiExtension for Store API domain handling - Add checkout-blocks.js for frontend domain field in blocks - Fix LicenseManager product type check in generateLicense() - Add multiple order status hooks for reliable license generation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
35
CLAUDE.md
35
CLAUDE.md
@@ -34,7 +34,13 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
|||||||
|
|
||||||
### Known Bugs
|
### Known Bugs
|
||||||
|
|
||||||
_No known bugs at this time.
|
_No known bugs at this time._
|
||||||
|
|
||||||
|
### Version 0.0.10
|
||||||
|
|
||||||
|
- Add a license related form section to the orders form in the admin area
|
||||||
|
- Make license domains editable in the backend
|
||||||
|
- Investigate checkout block integration improvements if issues persist
|
||||||
|
|
||||||
## Technical Stack
|
## Technical Stack
|
||||||
|
|
||||||
@@ -480,3 +486,30 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
|
|||||||
- JavaScript client works in both browser and Node.js environments
|
- JavaScript client works in both browser and Node.js environments
|
||||||
- Python client uses dataclasses for type-safe responses
|
- Python client uses dataclasses for type-safe responses
|
||||||
- C# client uses async/await patterns and System.Text.Json
|
- C# client uses async/await patterns and System.Text.Json
|
||||||
|
|
||||||
|
### 2026-01-21 - Bug Fixes (pre-v0.0.10)
|
||||||
|
|
||||||
|
**Fixed bugs:**
|
||||||
|
|
||||||
|
- Fixed: No licenses generated when order is marked as done
|
||||||
|
- Fixed: No domain field in WooCommerce Checkout Blocks
|
||||||
|
|
||||||
|
**New files:**
|
||||||
|
|
||||||
|
- `src/Checkout/CheckoutBlocksIntegration.php` - WooCommerce Blocks checkout integration
|
||||||
|
- `src/Checkout/StoreApiExtension.php` - Store API extension for domain field handling
|
||||||
|
- `assets/js/checkout-blocks.js` - Frontend JavaScript for checkout block domain field
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
|
||||||
|
- `src/Plugin.php` - Added checkout blocks registration and multiple order status hooks
|
||||||
|
- `src/License/LicenseManager.php` - Fixed product type check in `generateLicense()`
|
||||||
|
|
||||||
|
**Technical notes:**
|
||||||
|
|
||||||
|
- Added support for WooCommerce Checkout Blocks (default since WC 8.3+)
|
||||||
|
- `CheckoutBlocksIntegration` implements `IntegrationInterface` for block checkout
|
||||||
|
- `StoreApiExtension` handles server-side domain data via Store API
|
||||||
|
- License generation now triggers on `completed`, `processing`, and `payment_complete` hooks
|
||||||
|
- Fixed `LicenseManager::generateLicense()` to properly check product type with `is_type()`
|
||||||
|
- Classic checkout (`woocommerce_after_order_notes`) still works for stores using classic checkout
|
||||||
|
|||||||
100
assets/js/checkout-blocks.js
Normal file
100
assets/js/checkout-blocks.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* WooCommerce Checkout Blocks Integration
|
||||||
|
*
|
||||||
|
* Adds a domain field to the checkout block for licensed products.
|
||||||
|
*
|
||||||
|
* @package WcLicensedProduct
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { registerCheckoutBlock } = wc.blocksCheckout;
|
||||||
|
const { createElement, useState, useEffect } = wp.element;
|
||||||
|
const { TextControl } = wp.components;
|
||||||
|
const { __ } = wp.i18n;
|
||||||
|
const { extensionCartUpdate } = wc.blocksCheckout;
|
||||||
|
const { getSetting } = wc.wcSettings;
|
||||||
|
|
||||||
|
// Get settings passed from PHP
|
||||||
|
const settings = getSetting('wc-licensed-product_data', {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate domain format
|
||||||
|
*/
|
||||||
|
function isValidDomain(domain) {
|
||||||
|
if (!domain || domain.length > 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const pattern = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
||||||
|
return pattern.test(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize domain (remove protocol and www)
|
||||||
|
*/
|
||||||
|
function normalizeDomain(domain) {
|
||||||
|
let normalized = domain.toLowerCase().trim();
|
||||||
|
normalized = normalized.replace(/^https?:\/\//, '');
|
||||||
|
normalized = normalized.replace(/^www\./, '');
|
||||||
|
normalized = normalized.replace(/\/.*$/, '');
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* License Domain Block Component
|
||||||
|
*/
|
||||||
|
const LicenseDomainBlock = ({ checkoutExtensionData, extensions }) => {
|
||||||
|
const [domain, setDomain] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const { setExtensionData } = checkoutExtensionData;
|
||||||
|
|
||||||
|
// Only show if cart has licensed products
|
||||||
|
if (!settings.hasLicensedProducts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (value) => {
|
||||||
|
const normalized = normalizeDomain(value);
|
||||||
|
setDomain(normalized);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if (normalized && !isValidDomain(normalized)) {
|
||||||
|
setError(settings.validationError || __('Please enter a valid domain.', 'wc-licensed-product'));
|
||||||
|
} else {
|
||||||
|
setError('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update extension data for server-side processing
|
||||||
|
setExtensionData('wc-licensed-product', 'licensed_product_domain', normalized);
|
||||||
|
};
|
||||||
|
|
||||||
|
return createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'wc-block-components-licensed-product-domain' },
|
||||||
|
createElement(
|
||||||
|
'h3',
|
||||||
|
{ className: 'wc-block-components-title' },
|
||||||
|
settings.sectionTitle || __('License Domain', 'wc-licensed-product')
|
||||||
|
),
|
||||||
|
createElement(TextControl, {
|
||||||
|
label: settings.fieldLabel || __('Domain for License Activation', 'wc-licensed-product'),
|
||||||
|
value: domain,
|
||||||
|
onChange: handleChange,
|
||||||
|
placeholder: settings.fieldPlaceholder || 'example.com',
|
||||||
|
help: error || settings.fieldDescription || __('Enter the domain where you will use this license.', 'wc-licensed-product'),
|
||||||
|
className: error ? 'has-error' : '',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the checkout block
|
||||||
|
registerCheckoutBlock({
|
||||||
|
metadata: {
|
||||||
|
name: 'wc-licensed-product/domain-field',
|
||||||
|
parent: ['woocommerce/checkout-contact-information-block'],
|
||||||
|
},
|
||||||
|
component: LicenseDomainBlock,
|
||||||
|
});
|
||||||
|
})();
|
||||||
127
src/Checkout/CheckoutBlocksIntegration.php
Normal file
127
src/Checkout/CheckoutBlocksIntegration.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WooCommerce Checkout Blocks Integration
|
||||||
|
*
|
||||||
|
* @package Jeremias\WcLicensedProduct\Checkout
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Jeremias\WcLicensedProduct\Checkout;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration with WooCommerce Checkout Blocks
|
||||||
|
*/
|
||||||
|
final class CheckoutBlocksIntegration implements IntegrationInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name of the integration
|
||||||
|
*/
|
||||||
|
public function get_name(): string
|
||||||
|
{
|
||||||
|
return 'wc-licensed-product';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the integration
|
||||||
|
*/
|
||||||
|
public function initialize(): void
|
||||||
|
{
|
||||||
|
$this->registerScripts();
|
||||||
|
$this->registerBlockExtensionData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register scripts for the checkout block
|
||||||
|
*/
|
||||||
|
private function registerScripts(): void
|
||||||
|
{
|
||||||
|
$scriptPath = WC_LICENSED_PRODUCT_PLUGIN_DIR . 'assets/js/checkout-blocks.js';
|
||||||
|
$scriptUrl = WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/js/checkout-blocks.js';
|
||||||
|
|
||||||
|
if (file_exists($scriptPath)) {
|
||||||
|
wp_register_script(
|
||||||
|
'wc-licensed-product-checkout-blocks',
|
||||||
|
$scriptUrl,
|
||||||
|
['wc-blocks-checkout', 'wp-element', 'wp-components', 'wp-i18n'],
|
||||||
|
WC_LICENSED_PRODUCT_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_set_script_translations(
|
||||||
|
'wc-licensed-product-checkout-blocks',
|
||||||
|
'wc-licensed-product',
|
||||||
|
WC_LICENSED_PRODUCT_PLUGIN_DIR . 'languages'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register block extension data
|
||||||
|
*/
|
||||||
|
private function registerBlockExtensionData(): void
|
||||||
|
{
|
||||||
|
// Pass data to the checkout block script
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_blocks_checkout_block_registration_data',
|
||||||
|
function (array $data): array {
|
||||||
|
$data['wc-licensed-product'] = [
|
||||||
|
'hasLicensedProducts' => $this->cartHasLicensedProducts(),
|
||||||
|
];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of script handles to enqueue in the frontend context
|
||||||
|
*/
|
||||||
|
public function get_script_handles(): array
|
||||||
|
{
|
||||||
|
return ['wc-licensed-product-checkout-blocks'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of script handles to enqueue in the editor context
|
||||||
|
*/
|
||||||
|
public function get_editor_script_handles(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns script data to pass to the frontend scripts
|
||||||
|
*/
|
||||||
|
public function get_script_data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'hasLicensedProducts' => $this->cartHasLicensedProducts(),
|
||||||
|
'fieldLabel' => __('Domain for License Activation', 'wc-licensed-product'),
|
||||||
|
'fieldPlaceholder' => __('example.com', 'wc-licensed-product'),
|
||||||
|
'fieldDescription' => __('Enter the domain where you will use this license (without http:// or www).', 'wc-licensed-product'),
|
||||||
|
'sectionTitle' => __('License Domain', 'wc-licensed-product'),
|
||||||
|
'validationError' => __('Please enter a valid domain for your license activation.', 'wc-licensed-product'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cart contains licensed products
|
||||||
|
*/
|
||||||
|
private function cartHasLicensedProducts(): bool
|
||||||
|
{
|
||||||
|
if (!WC()->cart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (WC()->cart->get_cart() as $cartItem) {
|
||||||
|
$product = $cartItem['data'];
|
||||||
|
if ($product && $product->is_type('licensed')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/Checkout/StoreApiExtension.php
Normal file
134
src/Checkout/StoreApiExtension.php
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WooCommerce Store API Extension
|
||||||
|
*
|
||||||
|
* @package Jeremias\WcLicensedProduct\Checkout
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Jeremias\WcLicensedProduct\Checkout;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema;
|
||||||
|
use Automattic\WooCommerce\StoreApi\StoreApi;
|
||||||
|
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
|
||||||
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the Store API to handle licensed product domain data
|
||||||
|
*/
|
||||||
|
final class StoreApiExtension
|
||||||
|
{
|
||||||
|
private const IDENTIFIER = 'wc-licensed-product';
|
||||||
|
|
||||||
|
private LicenseManager $licenseManager;
|
||||||
|
|
||||||
|
public function __construct(LicenseManager $licenseManager)
|
||||||
|
{
|
||||||
|
$this->licenseManager = $licenseManager;
|
||||||
|
$this->registerHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Store API hooks
|
||||||
|
*/
|
||||||
|
private function registerHooks(): void
|
||||||
|
{
|
||||||
|
add_action('woocommerce_blocks_loaded', [$this, 'registerStoreApiExtension']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the Store API extension
|
||||||
|
*/
|
||||||
|
public function registerStoreApiExtension(): void
|
||||||
|
{
|
||||||
|
if (!class_exists('Automattic\WooCommerce\StoreApi\StoreApi')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register endpoint data extension
|
||||||
|
woocommerce_store_api_register_endpoint_data([
|
||||||
|
'endpoint' => CheckoutSchema::IDENTIFIER,
|
||||||
|
'namespace' => self::IDENTIFIER,
|
||||||
|
'data_callback' => [$this, 'getExtensionData'],
|
||||||
|
'schema_callback' => [$this, 'getExtensionSchema'],
|
||||||
|
'schema_type' => ARRAY_A,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Register update callback for the domain field
|
||||||
|
woocommerce_store_api_register_update_callback([
|
||||||
|
'namespace' => self::IDENTIFIER,
|
||||||
|
'callback' => [$this, 'handleExtensionUpdate'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Hook into checkout order processing
|
||||||
|
add_action('woocommerce_store_api_checkout_order_processed', [$this, 'processCheckoutOrder']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get extension data for the checkout endpoint
|
||||||
|
*/
|
||||||
|
public function getExtensionData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'licensed_product_domain' => WC()->session ? WC()->session->get('licensed_product_domain', '') : '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get extension schema
|
||||||
|
*/
|
||||||
|
public function getExtensionSchema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'licensed_product_domain' => [
|
||||||
|
'description' => __('Domain for license activation', 'wc-licensed-product'),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => ['view', 'edit'],
|
||||||
|
'readonly' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle extension data updates from the frontend
|
||||||
|
*/
|
||||||
|
public function handleExtensionUpdate(array $data): void
|
||||||
|
{
|
||||||
|
if (isset($data['licensed_product_domain'])) {
|
||||||
|
$domain = sanitize_text_field($data['licensed_product_domain']);
|
||||||
|
$normalizedDomain = $this->licenseManager->normalizeDomain($domain);
|
||||||
|
|
||||||
|
if (WC()->session) {
|
||||||
|
WC()->session->set('licensed_product_domain', $normalizedDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the checkout order - save domain to order meta
|
||||||
|
*/
|
||||||
|
public function processCheckoutOrder(\WC_Order $order): void
|
||||||
|
{
|
||||||
|
$domain = WC()->session ? WC()->session->get('licensed_product_domain', '') : '';
|
||||||
|
|
||||||
|
// Also check in the request data for block checkout
|
||||||
|
if (empty($domain)) {
|
||||||
|
$requestData = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (isset($requestData['extensions'][self::IDENTIFIER]['licensed_product_domain'])) {
|
||||||
|
$domain = sanitize_text_field($requestData['extensions'][self::IDENTIFIER]['licensed_product_domain']);
|
||||||
|
$domain = $this->licenseManager->normalizeDomain($domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($domain)) {
|
||||||
|
$order->update_meta_data('_licensed_product_domain', $domain);
|
||||||
|
$order->save();
|
||||||
|
|
||||||
|
// Clear session data
|
||||||
|
if (WC()->session) {
|
||||||
|
WC()->session->set('licensed_product_domain', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,10 +56,15 @@ class LicenseManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
$product = wc_get_product($productId);
|
$product = wc_get_product($productId);
|
||||||
if (!$product instanceof LicensedProduct) {
|
if (!$product || !$product->is_type('licensed')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we have the LicensedProduct instance for type hints
|
||||||
|
if (!$product instanceof LicensedProduct) {
|
||||||
|
$product = new LicensedProduct($productId);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate unique license key
|
// Generate unique license key
|
||||||
$licenseKey = $this->generateLicenseKey();
|
$licenseKey = $this->generateLicenseKey();
|
||||||
while ($this->getLicenseByKey($licenseKey)) {
|
while ($this->getLicenseByKey($licenseKey)) {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ use Jeremias\WcLicensedProduct\Admin\AdminController;
|
|||||||
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
||||||
use Jeremias\WcLicensedProduct\Admin\VersionAdminController;
|
use Jeremias\WcLicensedProduct\Admin\VersionAdminController;
|
||||||
use Jeremias\WcLicensedProduct\Api\RestApiController;
|
use Jeremias\WcLicensedProduct\Api\RestApiController;
|
||||||
|
use Jeremias\WcLicensedProduct\Checkout\CheckoutBlocksIntegration;
|
||||||
use Jeremias\WcLicensedProduct\Checkout\CheckoutController;
|
use Jeremias\WcLicensedProduct\Checkout\CheckoutController;
|
||||||
|
use Jeremias\WcLicensedProduct\Checkout\StoreApiExtension;
|
||||||
use Jeremias\WcLicensedProduct\Email\LicenseEmailController;
|
use Jeremias\WcLicensedProduct\Email\LicenseEmailController;
|
||||||
use Jeremias\WcLicensedProduct\Frontend\AccountController;
|
use Jeremias\WcLicensedProduct\Frontend\AccountController;
|
||||||
use Jeremias\WcLicensedProduct\Frontend\DownloadController;
|
use Jeremias\WcLicensedProduct\Frontend\DownloadController;
|
||||||
@@ -110,6 +112,8 @@ final class Plugin
|
|||||||
// Initialize controllers
|
// Initialize controllers
|
||||||
new LicensedProductType();
|
new LicensedProductType();
|
||||||
new CheckoutController($this->licenseManager);
|
new CheckoutController($this->licenseManager);
|
||||||
|
new StoreApiExtension($this->licenseManager);
|
||||||
|
$this->registerCheckoutBlocksIntegration();
|
||||||
$this->downloadController = new DownloadController($this->licenseManager, $this->versionManager);
|
$this->downloadController = new DownloadController($this->licenseManager, $this->versionManager);
|
||||||
new AccountController($this->twig, $this->licenseManager, $this->versionManager, $this->downloadController);
|
new AccountController($this->twig, $this->licenseManager, $this->versionManager, $this->downloadController);
|
||||||
new RestApiController($this->licenseManager);
|
new RestApiController($this->licenseManager);
|
||||||
@@ -122,13 +126,34 @@ final class Plugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register WooCommerce Checkout Blocks integration
|
||||||
|
*/
|
||||||
|
private function registerCheckoutBlocksIntegration(): void
|
||||||
|
{
|
||||||
|
add_action('woocommerce_blocks_loaded', function (): void {
|
||||||
|
if (class_exists('Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry')) {
|
||||||
|
add_action(
|
||||||
|
'woocommerce_blocks_checkout_block_registration',
|
||||||
|
function ($integration_registry): void {
|
||||||
|
$integration_registry->register(new CheckoutBlocksIntegration());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register plugin hooks
|
* Register plugin hooks
|
||||||
*/
|
*/
|
||||||
private function registerHooks(): void
|
private function registerHooks(): void
|
||||||
{
|
{
|
||||||
// Generate license on order completion
|
// Generate license on order completion (multiple hooks for compatibility)
|
||||||
add_action('woocommerce_order_status_completed', [$this, 'onOrderCompleted']);
|
add_action('woocommerce_order_status_completed', [$this, 'onOrderCompleted']);
|
||||||
|
add_action('woocommerce_order_status_processing', [$this, 'onOrderCompleted']);
|
||||||
|
|
||||||
|
// Also hook into payment complete for immediate license generation
|
||||||
|
add_action('woocommerce_payment_complete', [$this, 'onOrderCompleted']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user