| @@ -25,18 +25,21 @@ | ||
| 25 | 25 | (new \FluentCart\App\Hooks\Handlers\RetentionSnapshotHandler)->register(); |
| 26 | 26 | (new \FluentCart\App\Hooks\Handlers\ReminderHandler)->register(); |
| 27 | 27 | |
| 28 | 28 | (new \FluentCart\App\Hooks\Handlers\ShortCodes\ShopAppHandler)->register(); |
| 29 | -(new \FluentCart\App\Hooks\Handlers\ExportHandler)->register(); | |
| 30 | 29 | |
| 31 | 30 | (new FluentCart\App\Hooks\Handlers\CustomCheckout\CustomCheckout())->register(); |
| 32 | 31 | |
| 33 | 32 | // Tax Module Init |
| 34 | -(new \FluentCart\App\Modules\Tax\TaxModule())->register(); | |
| 33 | +add_action('init', function () { | |
| 34 | + (new \FluentCart\App\Modules\Tax\TaxModule())->register(); | |
| 35 | +}, 1); | |
| 35 | 36 | |
| 36 | 37 | // Turnstile Module Init |
| 37 | 38 | (new \FluentCart\App\Modules\Turnstile\TurnstileInit())->register(\FluentCart\App\App::getInstance()); |
| 38 | 39 | |
| 40 | +(new \FluentCart\App\Modules\FluentPlayer\FluentPlayerAdminAssets())->register(); | |
| 41 | + | |
| 39 | 42 | // Register Pro Gateways Promo |
| 40 | 43 | (new \FluentCart\App\Hooks\Handlers\PromoGatewaysHandler())->register(); |
| 41 | 44 | |
| 42 | 45 | // Register Addon Gateways |
| @@ -73,8 +76,9 @@ | ||
| 73 | 76 | \FluentCart\App\Hooks\Handlers\BlockEditors\Buttons\BuyNowButtonBlockEditor::register(); |
| 74 | 77 | \FluentCart\App\Hooks\Handlers\BlockEditors\Buttons\AddToCartButtonBlockEditor::register(); |
| 75 | 78 | |
| 76 | 79 | \FluentCart\App\Hooks\Handlers\BlockEditors\MiniCartBlockEditor::register(); |
| 80 | +\FluentCart\App\Hooks\Handlers\BlockEditors\Cart\CartBlockEditor::register(); | |
| 77 | 81 | \FluentCart\App\Hooks\Handlers\BlockEditors\ProductCategoriesListBlockEditor::register(); |
| 78 | 82 | \FluentCart\App\Hooks\Handlers\BlockEditors\RelatedProduct\RelatedProductBlockEditor::register(); |
| 79 | 83 | \FluentCart\App\Hooks\Handlers\ShortCodes\Buttons\DirectCheckoutShortcode::register(); |
| 80 | 84 | \FluentCart\App\Hooks\Handlers\ShortCodes\Buttons\AddToCartShortcode::register(); |
| @@ -81,11 +85,13 @@ | ||
| 81 | 85 | \FluentCart\App\Hooks\Handlers\ShortCodes\MiniCartShortcode::register(); |
| 82 | 86 | \FluentCart\App\Hooks\Handlers\ShortCodes\ProductCategoriesListShortcode::register(); |
| 83 | 87 | \FluentCart\App\Hooks\Handlers\ShortCodes\ProductTitleShortCode::register(); |
| 84 | 88 | \FluentCart\App\Hooks\Handlers\ShortCodes\ProductImageShortCode::register(); |
| 89 | +\FluentCart\App\Hooks\Handlers\ShortCodes\SingleProductShortCode::register(); | |
| 85 | 90 | |
| 86 | 91 | |
| 87 | 92 | \FluentCart\App\Hooks\Handlers\BlockEditors\ProductSkuBlockEditor::register(); |
| 93 | +\FluentCart\App\Hooks\Handlers\BlockEditors\ProductPackageDescriptionBlockEditor::register(); | |
| 88 | 94 | \FluentCart\App\Hooks\Handlers\BlockEditors\StoreLogoBlockEditor::register(); |
| 89 | 95 | \FluentCart\App\Hooks\Handlers\ShortCodes\StoreLogoShortCode::register(); |
| 90 | 96 | \FluentCart\App\Hooks\Handlers\BlockEditors\CustomerDashboardButtonBlockEditor::register(); |
| 91 | 97 | \FluentCart\App\Hooks\Handlers\ShortCodes\CustomerDashboardButtonShortcode::register(); |
| @@ -129,13 +135,21 @@ | ||
| 129 | 135 | return; |
| 130 | 136 | } |
| 131 | 137 | |
| 132 | 138 | $order = \FluentCart\App\Models\Order::find($orderId); |
| 133 | - if (!$order || $order->payment_status !== 'paid' || !$order->getMeta('action_scheduler_id')) { | |
| 139 | + if (!$order || $order->payment_status !== 'paid') { | |
| 134 | 140 | return; |
| 135 | 141 | } |
| 136 | 142 | |
| 137 | - $order->deleteMeta('action_scheduler_id'); | |
| 143 | + // Claim the run by deleting the meta and branching on the affected-row count. | |
| 144 | + // The Action Scheduler worker and the receipt-page AJAX nudge | |
| 145 | + // (IntegrationEventListener::runOrderActionsAjax) can arrive together, so a | |
| 146 | + // read-then-delete would let both pass the guard and fire order_paid_done | |
| 147 | + // twice. A single DELETE is atomic: MySQL hands the row to exactly one | |
| 148 | + // caller, and the loser sees 0 rows and returns. | |
| 149 | + if (!$order->deleteMeta('action_scheduler_id')) { | |
| 150 | + return; | |
| 151 | + } | |
| 138 | 152 | |
| 139 | 153 | $transaction = \FluentCart\App\Models\OrderTransaction::query() |
| 140 | 154 | ->where('order_id', $order->id) |
| 141 | 155 | ->where('status', \FluentCart\App\Helpers\Status::TRANSACTION_SUCCEEDED) |
| @@ -158,9 +172,8 @@ | ||
| 158 | 172 | do_action('fluent_cart/order_paid_done', $eventData); |
| 159 | 173 | |
| 160 | 174 | }; |
| 161 | 175 | add_action('fluent_cart/order_paid_async_private_handle', $orderPaidAsyncHandler, 1, 1); |
| 162 | -add_action('fluent_cart/order_paid_ansyc_private_handle', $orderPaidAsyncHandler, 1, 1); | |
| 163 | 176 | |
| 164 | 177 | |
| 165 | 178 | // |
| 166 | 179 | //$app->addAction('fluent_cart/orders_filter_customer', function ($query, $filters) { |
| @@ -174,8 +187,15 @@ | ||
| 174 | 187 | } |
| 175 | 188 | |
| 176 | 189 | \FluentCart\App\Modules\Subscriptions\SubscriptionModule::register(); |
| 177 | 190 | \FluentCart\App\Modules\Shipping\ShippingModule::register(); |
| 191 | +\FluentCart\App\Modules\StoreManagedRenewal\RenewalModule::register(); | |
| 192 | +(new \FluentCart\App\Hooks\Handlers\AttributesHandler())->register(); | |
| 193 | +(new \FluentCart\App\Hooks\Handlers\AdvancedVariationHandler())->register(); | |
| 194 | + | |
| 195 | +// MCP (Model Context Protocol) — operator AI tools. Ships OFF; enabled in | |
| 196 | +// Settings → Features & addon → MCP. See MCPInit::boot() for the bootstrap. | |
| 197 | +\FluentCart\App\Modules\MCP\MCPInit::boot(); | |
| 178 | 198 | |
| 179 | 199 | $app->ready(function () use ($app) { |
| 180 | 200 | \FluentCart\App\Models\Connection\ConnectionManager::connect($this->app); |
| 181 | 201 | }); |