From 7415248a31715ed44c435e92ca13d069d137b26c Mon Sep 17 00:00:00 2001 From: roble Date: Sat, 25 Apr 2026 21:58:30 +0100 Subject: [PATCH 1/6] feat: Add billing notifications and services - Implemented SubscriptionCancelledNotification, SubscriptionCreatedNotification, SubscriptionResumedNotification, and SubscriptionUpdatedNotification for handling subscription-related email notifications. - Created SubscriptionPolicy to manage subscription update permissions. - Developed BillingServiceProvider to register billing services and commands. - Introduced BillingService to handle checkout processing, webhooks, and subscription management. - Added StripeGateway for Stripe payment processing, including customer creation and checkout session management. - Implemented PaymentGatewayManager to manage payment gateway drivers. - Created Billable trait for associating users with billing customers. - Added a test for the DatabaseSeeder to ensure it runs correctly. --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- CLAUDE.md | 2 +- Taskfile.yml | 4 +-- app/Providers/RouteServiceProvider.php | 17 ---------- composer.json | 12 +++++-- database/seeders/DatabaseSeeder.php | 13 ++++++++ module.json | 15 --------- routes/api.php | 9 +++--- routes/navigation.php | 5 ++- routes/web.php | 32 ++++++++++--------- .../Console/ExpireCheckoutSessionsCommand.php | 0 .../Contracts/PaymentGatewayInterface.php | 0 {app => src}/Data/AddressData.php | 0 {app => src}/Data/CheckoutData.php | 0 {app => src}/Data/CheckoutResultData.php | 0 {app => src}/Data/CustomerData.php | 0 {app => src}/Data/PaymentMethodData.php | 0 {app => src}/Data/PaymentMethodDetails.php | 0 {app => src}/Data/WebhookData.php | 0 {app => src}/Enums/BillingScheme.php | 0 {app => src}/Enums/CheckoutSessionStatus.php | 0 {app => src}/Enums/Currency.php | 0 {app => src}/Enums/InvoiceStatus.php | 0 {app => src}/Enums/PaymentMethodType.php | 0 {app => src}/Enums/PaymentStatus.php | 0 {app => src}/Enums/SubscriptionStatus.php | 0 {app => src}/Enums/WebhookEventType.php | 0 {app => src}/Events/CheckoutCompleted.php | 0 {app => src}/Events/InvoicePaid.php | 0 {app => src}/Events/PaymentFailed.php | 0 {app => src}/Events/PaymentSucceeded.php | 0 {app => src}/Events/SubscriptionCancelled.php | 0 {app => src}/Events/SubscriptionCreated.php | 0 {app => src}/Events/SubscriptionResumed.php | 0 {app => src}/Events/SubscriptionUpdated.php | 0 {app => src}/Filament/BillingPlugin.php | 0 .../Filament/Pages/BillingDashboard.php | 0 .../Resources/Customers/CustomerResource.php | 0 .../Customers/Pages/ListCustomers.php | 0 .../Customers/Pages/ViewCustomer.php | 0 .../Customers/Schemas/CustomerInfolist.php | 0 .../Customers/Tables/CustomersTable.php | 0 .../Products/Pages/CreateProduct.php | 0 .../Resources/Products/Pages/EditProduct.php | 0 .../Resources/Products/Pages/ListProducts.php | 0 .../Resources/Products/Pages/ViewProduct.php | 0 .../Resources/Products/ProductResource.php | 0 .../Products/Schemas/ProductForm.php | 0 .../Products/Schemas/ProductInfolist.php | 0 .../Products/Tables/ProductsTable.php | 0 .../Subscriptions/Pages/ListSubscriptions.php | 0 .../Subscriptions/Pages/ViewSubscription.php | 0 .../Schemas/SubscriptionInfolist.php | 0 .../Subscriptions/SubscriptionResource.php | 0 .../Tables/SubscriptionsTable.php | 0 .../Widgets/BillingSaasStatsWidget.php | 0 .../Widgets/ConversionChartWidget.php | 0 .../Filament/Widgets/RevenueChartWidget.php | 0 .../Widgets/SubscriptionsChartWidget.php | 0 .../Controllers/BillingPlansController.php | 0 .../Controllers/BillingPortalController.php | 0 .../Http/Controllers/CheckoutController.php | 0 .../Controllers/SettingsBillingController.php | 0 .../Controllers/SubscriptionController.php | 0 .../Http/Controllers/WebhookController.php | 0 .../Http/Middleware/RedirectToRegister.php | 0 .../Http/Requests/SubscribeRequest.php | 0 .../SendPaymentFailedNotification.php | 0 .../SendPaymentSucceededNotification.php | 0 .../SendSubscriptionCancelledNotification.php | 0 .../SendSubscriptionCreatedNotification.php | 0 .../SendSubscriptionResumedNotification.php | 0 .../SendSubscriptionUpdatedNotification.php | 0 {app => src}/Listeners/SyncSubscriberRole.php | 0 {app => src}/Models/CheckoutSession.php | 0 {app => src}/Models/Customer.php | 0 {app => src}/Models/Invoice.php | 0 {app => src}/Models/Payment.php | 0 {app => src}/Models/PaymentMethod.php | 0 {app => src}/Models/PaymentProvider.php | 0 {app => src}/Models/Price.php | 0 {app => src}/Models/Product.php | 0 {app => src}/Models/Subscription.php | 0 {app => src}/Models/WebhookEvent.php | 0 .../PaymentFailedNotification.php | 0 .../PaymentSucceededNotification.php | 0 .../SubscriptionCancelledNotification.php | 0 .../SubscriptionCreatedNotification.php | 0 .../SubscriptionResumedNotification.php | 0 .../SubscriptionUpdatedNotification.php | 0 {app => src}/Policies/SubscriptionPolicy.php | 0 .../Providers/BillingServiceProvider.php | 12 ++----- {app => src}/Services/BillingService.php | 0 .../Services/Gateways/StripeGateway.php | 0 .../Services/PaymentGatewayManager.php | 0 {app => src}/Traits/Billable.php | 0 tests/Feature/DatabaseSeederTest.php | 19 +++++++++++ 98 files changed, 72 insertions(+), 72 deletions(-) delete mode 100644 app/Providers/RouteServiceProvider.php create mode 100644 database/seeders/DatabaseSeeder.php delete mode 100644 module.json rename {app => src}/Console/ExpireCheckoutSessionsCommand.php (100%) rename {app => src}/Contracts/PaymentGatewayInterface.php (100%) rename {app => src}/Data/AddressData.php (100%) rename {app => src}/Data/CheckoutData.php (100%) rename {app => src}/Data/CheckoutResultData.php (100%) rename {app => src}/Data/CustomerData.php (100%) rename {app => src}/Data/PaymentMethodData.php (100%) rename {app => src}/Data/PaymentMethodDetails.php (100%) rename {app => src}/Data/WebhookData.php (100%) rename {app => src}/Enums/BillingScheme.php (100%) rename {app => src}/Enums/CheckoutSessionStatus.php (100%) rename {app => src}/Enums/Currency.php (100%) rename {app => src}/Enums/InvoiceStatus.php (100%) rename {app => src}/Enums/PaymentMethodType.php (100%) rename {app => src}/Enums/PaymentStatus.php (100%) rename {app => src}/Enums/SubscriptionStatus.php (100%) rename {app => src}/Enums/WebhookEventType.php (100%) rename {app => src}/Events/CheckoutCompleted.php (100%) rename {app => src}/Events/InvoicePaid.php (100%) rename {app => src}/Events/PaymentFailed.php (100%) rename {app => src}/Events/PaymentSucceeded.php (100%) rename {app => src}/Events/SubscriptionCancelled.php (100%) rename {app => src}/Events/SubscriptionCreated.php (100%) rename {app => src}/Events/SubscriptionResumed.php (100%) rename {app => src}/Events/SubscriptionUpdated.php (100%) rename {app => src}/Filament/BillingPlugin.php (100%) rename {app => src}/Filament/Pages/BillingDashboard.php (100%) rename {app => src}/Filament/Resources/Customers/CustomerResource.php (100%) rename {app => src}/Filament/Resources/Customers/Pages/ListCustomers.php (100%) rename {app => src}/Filament/Resources/Customers/Pages/ViewCustomer.php (100%) rename {app => src}/Filament/Resources/Customers/Schemas/CustomerInfolist.php (100%) rename {app => src}/Filament/Resources/Customers/Tables/CustomersTable.php (100%) rename {app => src}/Filament/Resources/Products/Pages/CreateProduct.php (100%) rename {app => src}/Filament/Resources/Products/Pages/EditProduct.php (100%) rename {app => src}/Filament/Resources/Products/Pages/ListProducts.php (100%) rename {app => src}/Filament/Resources/Products/Pages/ViewProduct.php (100%) rename {app => src}/Filament/Resources/Products/ProductResource.php (100%) rename {app => src}/Filament/Resources/Products/Schemas/ProductForm.php (100%) rename {app => src}/Filament/Resources/Products/Schemas/ProductInfolist.php (100%) rename {app => src}/Filament/Resources/Products/Tables/ProductsTable.php (100%) rename {app => src}/Filament/Resources/Subscriptions/Pages/ListSubscriptions.php (100%) rename {app => src}/Filament/Resources/Subscriptions/Pages/ViewSubscription.php (100%) rename {app => src}/Filament/Resources/Subscriptions/Schemas/SubscriptionInfolist.php (100%) rename {app => src}/Filament/Resources/Subscriptions/SubscriptionResource.php (100%) rename {app => src}/Filament/Resources/Subscriptions/Tables/SubscriptionsTable.php (100%) rename {app => src}/Filament/Widgets/BillingSaasStatsWidget.php (100%) rename {app => src}/Filament/Widgets/ConversionChartWidget.php (100%) rename {app => src}/Filament/Widgets/RevenueChartWidget.php (100%) rename {app => src}/Filament/Widgets/SubscriptionsChartWidget.php (100%) rename {app => src}/Http/Controllers/BillingPlansController.php (100%) rename {app => src}/Http/Controllers/BillingPortalController.php (100%) rename {app => src}/Http/Controllers/CheckoutController.php (100%) rename {app => src}/Http/Controllers/SettingsBillingController.php (100%) rename {app => src}/Http/Controllers/SubscriptionController.php (100%) rename {app => src}/Http/Controllers/WebhookController.php (100%) rename {app => src}/Http/Middleware/RedirectToRegister.php (100%) rename {app => src}/Http/Requests/SubscribeRequest.php (100%) rename {app => src}/Listeners/SendPaymentFailedNotification.php (100%) rename {app => src}/Listeners/SendPaymentSucceededNotification.php (100%) rename {app => src}/Listeners/SendSubscriptionCancelledNotification.php (100%) rename {app => src}/Listeners/SendSubscriptionCreatedNotification.php (100%) rename {app => src}/Listeners/SendSubscriptionResumedNotification.php (100%) rename {app => src}/Listeners/SendSubscriptionUpdatedNotification.php (100%) rename {app => src}/Listeners/SyncSubscriberRole.php (100%) rename {app => src}/Models/CheckoutSession.php (100%) rename {app => src}/Models/Customer.php (100%) rename {app => src}/Models/Invoice.php (100%) rename {app => src}/Models/Payment.php (100%) rename {app => src}/Models/PaymentMethod.php (100%) rename {app => src}/Models/PaymentProvider.php (100%) rename {app => src}/Models/Price.php (100%) rename {app => src}/Models/Product.php (100%) rename {app => src}/Models/Subscription.php (100%) rename {app => src}/Models/WebhookEvent.php (100%) rename {app => src}/Notifications/PaymentFailedNotification.php (100%) rename {app => src}/Notifications/PaymentSucceededNotification.php (100%) rename {app => src}/Notifications/SubscriptionCancelledNotification.php (100%) rename {app => src}/Notifications/SubscriptionCreatedNotification.php (100%) rename {app => src}/Notifications/SubscriptionResumedNotification.php (100%) rename {app => src}/Notifications/SubscriptionUpdatedNotification.php (100%) rename {app => src}/Policies/SubscriptionPolicy.php (100%) rename {app => src}/Providers/BillingServiceProvider.php (77%) rename {app => src}/Services/BillingService.php (100%) rename {app => src}/Services/Gateways/StripeGateway.php (100%) rename {app => src}/Services/PaymentGatewayManager.php (100%) rename {app => src}/Traits/Billable.php (100%) create mode 100644 tests/Feature/DatabaseSeederTest.php diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6d0709..160d817 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: release: uses: saucebase-dev/saucebase/.github/workflows/semantic-release.yml@main with: - version_file: 'module.json' + version_file: 'composer.json' version_file_path: '.version' prerelease: false secrets: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67ad138..11d6355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,5 @@ jobs: test: uses: saucebase-dev/saucebase/.github/workflows/test-module.yml@main with: - module: Billing + module: billing dependencies: saucebase/auth diff --git a/CLAUDE.md b/CLAUDE.md index 434f920..623a607 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,7 +98,7 @@ BILLING_LOG_CHANNEL=stack ```bash php artisan test --testsuite=Modules --filter='^Modules\\Billing\\Tests' # PHPUnit -npx playwright test --project="@Billing*" # E2E +npx playwright test --project="@billing*" # E2E ``` ## Debugging Billing Issues diff --git a/Taskfile.yml b/Taskfile.yml index eca6ab9..4b98403 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -15,11 +15,11 @@ tasks: test:e2e: desc: Run E2E tests for Billing module - cmd: npx playwright test --project="@Billing*" {{.CLI_ARGS}} + cmd: npx playwright test --project="@billing*" {{.CLI_ARGS}} interactive: true # ── Code Generation ──────────────────────────────────────────── types:generate: desc: Generate TypeScript types from PHP DTOs and enums - cmd: php artisan module:generate-types Billing + cmd: php artisan module:generate-types billing diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php deleted file mode 100644 index cffae69..0000000 --- a/app/Providers/RouteServiceProvider.php +++ /dev/null @@ -1,17 +0,0 @@ -group(module_path('Billing', '/routes/web.php')); - Route::middleware('api') - ->group(module_path('Billing', '/routes/api.php')); - } -} diff --git a/composer.json b/composer.json index bc02a47..24bf8d5 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "name": "saucebase/billing", "description": "Payment gateway and subscription management module for Saucebase", "type": "saucebase-module", + "version": "1.7.0", "authors": [ { "name": "Saucebase", @@ -14,17 +15,22 @@ "stripe/stripe-php": "^19.2", "spatie/laravel-typescript-transformer": "^2.5" }, + "license": "proprietary", + "minimum-stability": "stable", "extra": { "laravel": { - "providers": [], + "providers": [ + "Modules\\Billing\\Providers\\BillingServiceProvider" + ], "aliases": {} } }, "autoload": { "psr-4": { - "Modules\\Billing\\": "app/", + "Modules\\Billing\\": "src/", "Modules\\Billing\\Database\\Factories\\": "database/factories/", - "Modules\\Billing\\Database\\Seeders\\": "database/seeders/" + "Modules\\Billing\\Database\\Seeders\\": "database/seeders/", + "Modules\\Billing\\Tests\\Support\\": "tests/Support/" } }, "autoload-dev": { diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..e0642ec --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,13 @@ +call(BillingDatabaseSeeder::class); + } +} diff --git a/module.json b/module.json deleted file mode 100644 index 83e8154..0000000 --- a/module.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "Billing", - "alias": "billing", - "description": "Billing module", - "author": "Saucebase", - "version": "v1.7.0", - "keywords": [ - "billing" - ], - "priority": 0, - "providers": [ - "Modules\\Billing\\Providers\\BillingServiceProvider" - ], - "files": [] -} diff --git a/routes/api.php b/routes/api.php index 38d3fbe..fc1900b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,7 +4,8 @@ use Illuminate\Support\Facades\Route; use Modules\Billing\Http\Controllers\WebhookController; -// Webhook routes without CSRF protection -Route::post('/billing/webhooks/{provider}', WebhookController::class) - ->name('billing.webhooks') - ->withoutMiddleware([PreventRequestForgery::class]); +Route::middleware('api')->group(function (): void { + Route::post('/billing/webhooks/{provider}', WebhookController::class) + ->name('billing.webhooks') + ->withoutMiddleware([PreventRequestForgery::class]); +}); diff --git a/routes/navigation.php b/routes/navigation.php index c1c5f44..62dd45f 100644 --- a/routes/navigation.php +++ b/routes/navigation.php @@ -33,7 +33,7 @@ Navigation::addWhen( fn () => ! Auth::user()?->isSubscriber(), 'Upgrade', - route('billing.plans'), + fn () => route('billing.plans'), function (Section $section) { $section->attributes([ 'group' => 'user', @@ -45,8 +45,7 @@ function (Section $section) { } ); -// Settings sidebar - Billing -Navigation::add('Billing', route('settings.billing'), function (Section $section) { +Navigation::add('Billing', fn () => route('settings.billing'), function (Section $section) { $section->attributes([ 'group' => 'settings', 'slug' => 'billing', diff --git a/routes/web.php b/routes/web.php index 9a63588..361c672 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,23 +8,25 @@ use Modules\Billing\Http\Controllers\SubscriptionController; use Modules\Billing\Http\Middleware\RedirectToRegister; -Route::get('/pricing', BillingPlansController::class)->name('billing.plans'); +Route::middleware('web')->group(function (): void { + Route::get('/pricing', BillingPlansController::class)->name('billing.plans'); -Route::post('/billing/checkout', [CheckoutController::class, 'create'])->middleware('throttle:10,1')->name('billing.checkout.create'); + Route::post('/billing/checkout', [CheckoutController::class, 'create'])->middleware('throttle:10,1')->name('billing.checkout.create'); -Route::middleware(RedirectToRegister::class)->group(function () { - Route::get('/billing/checkout/{checkout_session}', [CheckoutController::class, 'show']) - ->name('billing.checkout') - ->missing(fn () => auth()->guest() ? redirect()->route('register') : abort(404)); - Route::post('/billing/checkout/{checkout_session}', [CheckoutController::class, 'store']) - ->name('billing.checkout.store') - ->missing(fn () => auth()->guest() ? redirect()->route('register') : abort(404)); -}); + Route::middleware(RedirectToRegister::class)->group(function (): void { + Route::get('/billing/checkout/{checkout_session}', [CheckoutController::class, 'show']) + ->name('billing.checkout') + ->missing(fn () => auth()->guest() ? redirect()->route('register') : abort(404)); + Route::post('/billing/checkout/{checkout_session}', [CheckoutController::class, 'store']) + ->name('billing.checkout.store') + ->missing(fn () => auth()->guest() ? redirect()->route('register') : abort(404)); + }); -Route::middleware('auth')->group(function () { - Route::get('/billing/portal', BillingPortalController::class)->name('billing.portal'); - Route::post('/billing/subscription/cancel', [SubscriptionController::class, 'cancel'])->name('billing.subscription.cancel'); - Route::post('/billing/subscription/resume', [SubscriptionController::class, 'resume'])->name('billing.subscription.resume'); + Route::middleware('auth')->group(function (): void { + Route::get('/billing/portal', BillingPortalController::class)->name('billing.portal'); + Route::post('/billing/subscription/cancel', [SubscriptionController::class, 'cancel'])->name('billing.subscription.cancel'); + Route::post('/billing/subscription/resume', [SubscriptionController::class, 'resume'])->name('billing.subscription.resume'); - Route::get('/settings/billing', [SettingsBillingController::class, 'show'])->name('settings.billing'); + Route::get('/settings/billing', [SettingsBillingController::class, 'show'])->name('settings.billing'); + }); }); diff --git a/app/Console/ExpireCheckoutSessionsCommand.php b/src/Console/ExpireCheckoutSessionsCommand.php similarity index 100% rename from app/Console/ExpireCheckoutSessionsCommand.php rename to src/Console/ExpireCheckoutSessionsCommand.php diff --git a/app/Contracts/PaymentGatewayInterface.php b/src/Contracts/PaymentGatewayInterface.php similarity index 100% rename from app/Contracts/PaymentGatewayInterface.php rename to src/Contracts/PaymentGatewayInterface.php diff --git a/app/Data/AddressData.php b/src/Data/AddressData.php similarity index 100% rename from app/Data/AddressData.php rename to src/Data/AddressData.php diff --git a/app/Data/CheckoutData.php b/src/Data/CheckoutData.php similarity index 100% rename from app/Data/CheckoutData.php rename to src/Data/CheckoutData.php diff --git a/app/Data/CheckoutResultData.php b/src/Data/CheckoutResultData.php similarity index 100% rename from app/Data/CheckoutResultData.php rename to src/Data/CheckoutResultData.php diff --git a/app/Data/CustomerData.php b/src/Data/CustomerData.php similarity index 100% rename from app/Data/CustomerData.php rename to src/Data/CustomerData.php diff --git a/app/Data/PaymentMethodData.php b/src/Data/PaymentMethodData.php similarity index 100% rename from app/Data/PaymentMethodData.php rename to src/Data/PaymentMethodData.php diff --git a/app/Data/PaymentMethodDetails.php b/src/Data/PaymentMethodDetails.php similarity index 100% rename from app/Data/PaymentMethodDetails.php rename to src/Data/PaymentMethodDetails.php diff --git a/app/Data/WebhookData.php b/src/Data/WebhookData.php similarity index 100% rename from app/Data/WebhookData.php rename to src/Data/WebhookData.php diff --git a/app/Enums/BillingScheme.php b/src/Enums/BillingScheme.php similarity index 100% rename from app/Enums/BillingScheme.php rename to src/Enums/BillingScheme.php diff --git a/app/Enums/CheckoutSessionStatus.php b/src/Enums/CheckoutSessionStatus.php similarity index 100% rename from app/Enums/CheckoutSessionStatus.php rename to src/Enums/CheckoutSessionStatus.php diff --git a/app/Enums/Currency.php b/src/Enums/Currency.php similarity index 100% rename from app/Enums/Currency.php rename to src/Enums/Currency.php diff --git a/app/Enums/InvoiceStatus.php b/src/Enums/InvoiceStatus.php similarity index 100% rename from app/Enums/InvoiceStatus.php rename to src/Enums/InvoiceStatus.php diff --git a/app/Enums/PaymentMethodType.php b/src/Enums/PaymentMethodType.php similarity index 100% rename from app/Enums/PaymentMethodType.php rename to src/Enums/PaymentMethodType.php diff --git a/app/Enums/PaymentStatus.php b/src/Enums/PaymentStatus.php similarity index 100% rename from app/Enums/PaymentStatus.php rename to src/Enums/PaymentStatus.php diff --git a/app/Enums/SubscriptionStatus.php b/src/Enums/SubscriptionStatus.php similarity index 100% rename from app/Enums/SubscriptionStatus.php rename to src/Enums/SubscriptionStatus.php diff --git a/app/Enums/WebhookEventType.php b/src/Enums/WebhookEventType.php similarity index 100% rename from app/Enums/WebhookEventType.php rename to src/Enums/WebhookEventType.php diff --git a/app/Events/CheckoutCompleted.php b/src/Events/CheckoutCompleted.php similarity index 100% rename from app/Events/CheckoutCompleted.php rename to src/Events/CheckoutCompleted.php diff --git a/app/Events/InvoicePaid.php b/src/Events/InvoicePaid.php similarity index 100% rename from app/Events/InvoicePaid.php rename to src/Events/InvoicePaid.php diff --git a/app/Events/PaymentFailed.php b/src/Events/PaymentFailed.php similarity index 100% rename from app/Events/PaymentFailed.php rename to src/Events/PaymentFailed.php diff --git a/app/Events/PaymentSucceeded.php b/src/Events/PaymentSucceeded.php similarity index 100% rename from app/Events/PaymentSucceeded.php rename to src/Events/PaymentSucceeded.php diff --git a/app/Events/SubscriptionCancelled.php b/src/Events/SubscriptionCancelled.php similarity index 100% rename from app/Events/SubscriptionCancelled.php rename to src/Events/SubscriptionCancelled.php diff --git a/app/Events/SubscriptionCreated.php b/src/Events/SubscriptionCreated.php similarity index 100% rename from app/Events/SubscriptionCreated.php rename to src/Events/SubscriptionCreated.php diff --git a/app/Events/SubscriptionResumed.php b/src/Events/SubscriptionResumed.php similarity index 100% rename from app/Events/SubscriptionResumed.php rename to src/Events/SubscriptionResumed.php diff --git a/app/Events/SubscriptionUpdated.php b/src/Events/SubscriptionUpdated.php similarity index 100% rename from app/Events/SubscriptionUpdated.php rename to src/Events/SubscriptionUpdated.php diff --git a/app/Filament/BillingPlugin.php b/src/Filament/BillingPlugin.php similarity index 100% rename from app/Filament/BillingPlugin.php rename to src/Filament/BillingPlugin.php diff --git a/app/Filament/Pages/BillingDashboard.php b/src/Filament/Pages/BillingDashboard.php similarity index 100% rename from app/Filament/Pages/BillingDashboard.php rename to src/Filament/Pages/BillingDashboard.php diff --git a/app/Filament/Resources/Customers/CustomerResource.php b/src/Filament/Resources/Customers/CustomerResource.php similarity index 100% rename from app/Filament/Resources/Customers/CustomerResource.php rename to src/Filament/Resources/Customers/CustomerResource.php diff --git a/app/Filament/Resources/Customers/Pages/ListCustomers.php b/src/Filament/Resources/Customers/Pages/ListCustomers.php similarity index 100% rename from app/Filament/Resources/Customers/Pages/ListCustomers.php rename to src/Filament/Resources/Customers/Pages/ListCustomers.php diff --git a/app/Filament/Resources/Customers/Pages/ViewCustomer.php b/src/Filament/Resources/Customers/Pages/ViewCustomer.php similarity index 100% rename from app/Filament/Resources/Customers/Pages/ViewCustomer.php rename to src/Filament/Resources/Customers/Pages/ViewCustomer.php diff --git a/app/Filament/Resources/Customers/Schemas/CustomerInfolist.php b/src/Filament/Resources/Customers/Schemas/CustomerInfolist.php similarity index 100% rename from app/Filament/Resources/Customers/Schemas/CustomerInfolist.php rename to src/Filament/Resources/Customers/Schemas/CustomerInfolist.php diff --git a/app/Filament/Resources/Customers/Tables/CustomersTable.php b/src/Filament/Resources/Customers/Tables/CustomersTable.php similarity index 100% rename from app/Filament/Resources/Customers/Tables/CustomersTable.php rename to src/Filament/Resources/Customers/Tables/CustomersTable.php diff --git a/app/Filament/Resources/Products/Pages/CreateProduct.php b/src/Filament/Resources/Products/Pages/CreateProduct.php similarity index 100% rename from app/Filament/Resources/Products/Pages/CreateProduct.php rename to src/Filament/Resources/Products/Pages/CreateProduct.php diff --git a/app/Filament/Resources/Products/Pages/EditProduct.php b/src/Filament/Resources/Products/Pages/EditProduct.php similarity index 100% rename from app/Filament/Resources/Products/Pages/EditProduct.php rename to src/Filament/Resources/Products/Pages/EditProduct.php diff --git a/app/Filament/Resources/Products/Pages/ListProducts.php b/src/Filament/Resources/Products/Pages/ListProducts.php similarity index 100% rename from app/Filament/Resources/Products/Pages/ListProducts.php rename to src/Filament/Resources/Products/Pages/ListProducts.php diff --git a/app/Filament/Resources/Products/Pages/ViewProduct.php b/src/Filament/Resources/Products/Pages/ViewProduct.php similarity index 100% rename from app/Filament/Resources/Products/Pages/ViewProduct.php rename to src/Filament/Resources/Products/Pages/ViewProduct.php diff --git a/app/Filament/Resources/Products/ProductResource.php b/src/Filament/Resources/Products/ProductResource.php similarity index 100% rename from app/Filament/Resources/Products/ProductResource.php rename to src/Filament/Resources/Products/ProductResource.php diff --git a/app/Filament/Resources/Products/Schemas/ProductForm.php b/src/Filament/Resources/Products/Schemas/ProductForm.php similarity index 100% rename from app/Filament/Resources/Products/Schemas/ProductForm.php rename to src/Filament/Resources/Products/Schemas/ProductForm.php diff --git a/app/Filament/Resources/Products/Schemas/ProductInfolist.php b/src/Filament/Resources/Products/Schemas/ProductInfolist.php similarity index 100% rename from app/Filament/Resources/Products/Schemas/ProductInfolist.php rename to src/Filament/Resources/Products/Schemas/ProductInfolist.php diff --git a/app/Filament/Resources/Products/Tables/ProductsTable.php b/src/Filament/Resources/Products/Tables/ProductsTable.php similarity index 100% rename from app/Filament/Resources/Products/Tables/ProductsTable.php rename to src/Filament/Resources/Products/Tables/ProductsTable.php diff --git a/app/Filament/Resources/Subscriptions/Pages/ListSubscriptions.php b/src/Filament/Resources/Subscriptions/Pages/ListSubscriptions.php similarity index 100% rename from app/Filament/Resources/Subscriptions/Pages/ListSubscriptions.php rename to src/Filament/Resources/Subscriptions/Pages/ListSubscriptions.php diff --git a/app/Filament/Resources/Subscriptions/Pages/ViewSubscription.php b/src/Filament/Resources/Subscriptions/Pages/ViewSubscription.php similarity index 100% rename from app/Filament/Resources/Subscriptions/Pages/ViewSubscription.php rename to src/Filament/Resources/Subscriptions/Pages/ViewSubscription.php diff --git a/app/Filament/Resources/Subscriptions/Schemas/SubscriptionInfolist.php b/src/Filament/Resources/Subscriptions/Schemas/SubscriptionInfolist.php similarity index 100% rename from app/Filament/Resources/Subscriptions/Schemas/SubscriptionInfolist.php rename to src/Filament/Resources/Subscriptions/Schemas/SubscriptionInfolist.php diff --git a/app/Filament/Resources/Subscriptions/SubscriptionResource.php b/src/Filament/Resources/Subscriptions/SubscriptionResource.php similarity index 100% rename from app/Filament/Resources/Subscriptions/SubscriptionResource.php rename to src/Filament/Resources/Subscriptions/SubscriptionResource.php diff --git a/app/Filament/Resources/Subscriptions/Tables/SubscriptionsTable.php b/src/Filament/Resources/Subscriptions/Tables/SubscriptionsTable.php similarity index 100% rename from app/Filament/Resources/Subscriptions/Tables/SubscriptionsTable.php rename to src/Filament/Resources/Subscriptions/Tables/SubscriptionsTable.php diff --git a/app/Filament/Widgets/BillingSaasStatsWidget.php b/src/Filament/Widgets/BillingSaasStatsWidget.php similarity index 100% rename from app/Filament/Widgets/BillingSaasStatsWidget.php rename to src/Filament/Widgets/BillingSaasStatsWidget.php diff --git a/app/Filament/Widgets/ConversionChartWidget.php b/src/Filament/Widgets/ConversionChartWidget.php similarity index 100% rename from app/Filament/Widgets/ConversionChartWidget.php rename to src/Filament/Widgets/ConversionChartWidget.php diff --git a/app/Filament/Widgets/RevenueChartWidget.php b/src/Filament/Widgets/RevenueChartWidget.php similarity index 100% rename from app/Filament/Widgets/RevenueChartWidget.php rename to src/Filament/Widgets/RevenueChartWidget.php diff --git a/app/Filament/Widgets/SubscriptionsChartWidget.php b/src/Filament/Widgets/SubscriptionsChartWidget.php similarity index 100% rename from app/Filament/Widgets/SubscriptionsChartWidget.php rename to src/Filament/Widgets/SubscriptionsChartWidget.php diff --git a/app/Http/Controllers/BillingPlansController.php b/src/Http/Controllers/BillingPlansController.php similarity index 100% rename from app/Http/Controllers/BillingPlansController.php rename to src/Http/Controllers/BillingPlansController.php diff --git a/app/Http/Controllers/BillingPortalController.php b/src/Http/Controllers/BillingPortalController.php similarity index 100% rename from app/Http/Controllers/BillingPortalController.php rename to src/Http/Controllers/BillingPortalController.php diff --git a/app/Http/Controllers/CheckoutController.php b/src/Http/Controllers/CheckoutController.php similarity index 100% rename from app/Http/Controllers/CheckoutController.php rename to src/Http/Controllers/CheckoutController.php diff --git a/app/Http/Controllers/SettingsBillingController.php b/src/Http/Controllers/SettingsBillingController.php similarity index 100% rename from app/Http/Controllers/SettingsBillingController.php rename to src/Http/Controllers/SettingsBillingController.php diff --git a/app/Http/Controllers/SubscriptionController.php b/src/Http/Controllers/SubscriptionController.php similarity index 100% rename from app/Http/Controllers/SubscriptionController.php rename to src/Http/Controllers/SubscriptionController.php diff --git a/app/Http/Controllers/WebhookController.php b/src/Http/Controllers/WebhookController.php similarity index 100% rename from app/Http/Controllers/WebhookController.php rename to src/Http/Controllers/WebhookController.php diff --git a/app/Http/Middleware/RedirectToRegister.php b/src/Http/Middleware/RedirectToRegister.php similarity index 100% rename from app/Http/Middleware/RedirectToRegister.php rename to src/Http/Middleware/RedirectToRegister.php diff --git a/app/Http/Requests/SubscribeRequest.php b/src/Http/Requests/SubscribeRequest.php similarity index 100% rename from app/Http/Requests/SubscribeRequest.php rename to src/Http/Requests/SubscribeRequest.php diff --git a/app/Listeners/SendPaymentFailedNotification.php b/src/Listeners/SendPaymentFailedNotification.php similarity index 100% rename from app/Listeners/SendPaymentFailedNotification.php rename to src/Listeners/SendPaymentFailedNotification.php diff --git a/app/Listeners/SendPaymentSucceededNotification.php b/src/Listeners/SendPaymentSucceededNotification.php similarity index 100% rename from app/Listeners/SendPaymentSucceededNotification.php rename to src/Listeners/SendPaymentSucceededNotification.php diff --git a/app/Listeners/SendSubscriptionCancelledNotification.php b/src/Listeners/SendSubscriptionCancelledNotification.php similarity index 100% rename from app/Listeners/SendSubscriptionCancelledNotification.php rename to src/Listeners/SendSubscriptionCancelledNotification.php diff --git a/app/Listeners/SendSubscriptionCreatedNotification.php b/src/Listeners/SendSubscriptionCreatedNotification.php similarity index 100% rename from app/Listeners/SendSubscriptionCreatedNotification.php rename to src/Listeners/SendSubscriptionCreatedNotification.php diff --git a/app/Listeners/SendSubscriptionResumedNotification.php b/src/Listeners/SendSubscriptionResumedNotification.php similarity index 100% rename from app/Listeners/SendSubscriptionResumedNotification.php rename to src/Listeners/SendSubscriptionResumedNotification.php diff --git a/app/Listeners/SendSubscriptionUpdatedNotification.php b/src/Listeners/SendSubscriptionUpdatedNotification.php similarity index 100% rename from app/Listeners/SendSubscriptionUpdatedNotification.php rename to src/Listeners/SendSubscriptionUpdatedNotification.php diff --git a/app/Listeners/SyncSubscriberRole.php b/src/Listeners/SyncSubscriberRole.php similarity index 100% rename from app/Listeners/SyncSubscriberRole.php rename to src/Listeners/SyncSubscriberRole.php diff --git a/app/Models/CheckoutSession.php b/src/Models/CheckoutSession.php similarity index 100% rename from app/Models/CheckoutSession.php rename to src/Models/CheckoutSession.php diff --git a/app/Models/Customer.php b/src/Models/Customer.php similarity index 100% rename from app/Models/Customer.php rename to src/Models/Customer.php diff --git a/app/Models/Invoice.php b/src/Models/Invoice.php similarity index 100% rename from app/Models/Invoice.php rename to src/Models/Invoice.php diff --git a/app/Models/Payment.php b/src/Models/Payment.php similarity index 100% rename from app/Models/Payment.php rename to src/Models/Payment.php diff --git a/app/Models/PaymentMethod.php b/src/Models/PaymentMethod.php similarity index 100% rename from app/Models/PaymentMethod.php rename to src/Models/PaymentMethod.php diff --git a/app/Models/PaymentProvider.php b/src/Models/PaymentProvider.php similarity index 100% rename from app/Models/PaymentProvider.php rename to src/Models/PaymentProvider.php diff --git a/app/Models/Price.php b/src/Models/Price.php similarity index 100% rename from app/Models/Price.php rename to src/Models/Price.php diff --git a/app/Models/Product.php b/src/Models/Product.php similarity index 100% rename from app/Models/Product.php rename to src/Models/Product.php diff --git a/app/Models/Subscription.php b/src/Models/Subscription.php similarity index 100% rename from app/Models/Subscription.php rename to src/Models/Subscription.php diff --git a/app/Models/WebhookEvent.php b/src/Models/WebhookEvent.php similarity index 100% rename from app/Models/WebhookEvent.php rename to src/Models/WebhookEvent.php diff --git a/app/Notifications/PaymentFailedNotification.php b/src/Notifications/PaymentFailedNotification.php similarity index 100% rename from app/Notifications/PaymentFailedNotification.php rename to src/Notifications/PaymentFailedNotification.php diff --git a/app/Notifications/PaymentSucceededNotification.php b/src/Notifications/PaymentSucceededNotification.php similarity index 100% rename from app/Notifications/PaymentSucceededNotification.php rename to src/Notifications/PaymentSucceededNotification.php diff --git a/app/Notifications/SubscriptionCancelledNotification.php b/src/Notifications/SubscriptionCancelledNotification.php similarity index 100% rename from app/Notifications/SubscriptionCancelledNotification.php rename to src/Notifications/SubscriptionCancelledNotification.php diff --git a/app/Notifications/SubscriptionCreatedNotification.php b/src/Notifications/SubscriptionCreatedNotification.php similarity index 100% rename from app/Notifications/SubscriptionCreatedNotification.php rename to src/Notifications/SubscriptionCreatedNotification.php diff --git a/app/Notifications/SubscriptionResumedNotification.php b/src/Notifications/SubscriptionResumedNotification.php similarity index 100% rename from app/Notifications/SubscriptionResumedNotification.php rename to src/Notifications/SubscriptionResumedNotification.php diff --git a/app/Notifications/SubscriptionUpdatedNotification.php b/src/Notifications/SubscriptionUpdatedNotification.php similarity index 100% rename from app/Notifications/SubscriptionUpdatedNotification.php rename to src/Notifications/SubscriptionUpdatedNotification.php diff --git a/app/Policies/SubscriptionPolicy.php b/src/Policies/SubscriptionPolicy.php similarity index 100% rename from app/Policies/SubscriptionPolicy.php rename to src/Policies/SubscriptionPolicy.php diff --git a/app/Providers/BillingServiceProvider.php b/src/Providers/BillingServiceProvider.php similarity index 77% rename from app/Providers/BillingServiceProvider.php rename to src/Providers/BillingServiceProvider.php index 38acdd9..77f5209 100644 --- a/app/Providers/BillingServiceProvider.php +++ b/src/Providers/BillingServiceProvider.php @@ -11,14 +11,6 @@ class BillingServiceProvider extends ModuleServiceProvider { - protected string $name = 'Billing'; - - protected string $nameLower = 'billing'; - - protected array $providers = [ - RouteServiceProvider::class, - ]; - protected array $commands = [ ExpireCheckoutSessionsCommand::class, ]; @@ -40,7 +32,7 @@ public function boot(): void { parent::boot(); - $this->loadViewsFrom(module_path($this->name, 'resources/views'), $this->nameLower); + $this->loadViewsFrom(module_path('billing', 'resources/views'), 'billing'); } /** @@ -50,7 +42,7 @@ protected function registerConfig(): void { parent::registerConfig(); - $this->mergeConfigFrom(module_path($this->name, 'config/services.php'), 'services'); + $this->mergeConfigFrom(module_path('billing', 'config/services.php'), 'services'); } protected function configureSchedules(Schedule $schedule): void diff --git a/app/Services/BillingService.php b/src/Services/BillingService.php similarity index 100% rename from app/Services/BillingService.php rename to src/Services/BillingService.php diff --git a/app/Services/Gateways/StripeGateway.php b/src/Services/Gateways/StripeGateway.php similarity index 100% rename from app/Services/Gateways/StripeGateway.php rename to src/Services/Gateways/StripeGateway.php diff --git a/app/Services/PaymentGatewayManager.php b/src/Services/PaymentGatewayManager.php similarity index 100% rename from app/Services/PaymentGatewayManager.php rename to src/Services/PaymentGatewayManager.php diff --git a/app/Traits/Billable.php b/src/Traits/Billable.php similarity index 100% rename from app/Traits/Billable.php rename to src/Traits/Billable.php diff --git a/tests/Feature/DatabaseSeederTest.php b/tests/Feature/DatabaseSeederTest.php new file mode 100644 index 0000000..e335f6a --- /dev/null +++ b/tests/Feature/DatabaseSeederTest.php @@ -0,0 +1,19 @@ +seed(DatabaseSeeder::class); + + $this->assertTrue(true); + } +} From fede990bbbf8746a26e5ab4a306418159e05ff45 Mon Sep 17 00:00:00 2001 From: roble Date: Tue, 28 Apr 2026 20:47:49 +0100 Subject: [PATCH 2/6] feat: Add getNavigationGroupSort method to BillingPlugin --- src/Filament/BillingPlugin.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Filament/BillingPlugin.php b/src/Filament/BillingPlugin.php index 82a2738..e25544e 100644 --- a/src/Filament/BillingPlugin.php +++ b/src/Filament/BillingPlugin.php @@ -22,6 +22,11 @@ public function getId(): string return 'billing'; } + public static function getNavigationGroupSort(): int + { + return 1; + } + public function boot(Panel $panel): void { $panel->navigationGroups([ From 44a59a3d2a1c1539aeefc01d2061f3274d1298f8 Mon Sep 17 00:00:00 2001 From: roble Date: Wed, 29 Apr 2026 21:37:11 +0100 Subject: [PATCH 3/6] feat: Add database seeding task for Billing module --- Taskfile.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 4b98403..eca8299 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -18,6 +18,12 @@ tasks: cmd: npx playwright test --project="@billing*" {{.CLI_ARGS}} interactive: true + # ── Database ────────────────────────────────────────────────── + + db:seed: + desc: Seed the Billing module database + cmd: php artisan modules:seed --module=billing + # ── Code Generation ──────────────────────────────────────────── types:generate: From 58f483919e97d0bea0604a56b5ca23b312a73f34 Mon Sep 17 00:00:00 2001 From: roble Date: Fri, 22 May 2026 21:33:23 +0100 Subject: [PATCH 4/6] feat: Refactor billing module structure and add new components for checkout and plans --- resources/js/app.ts | 25 +------------------ resources/js/vue/app.ts | 24 ++++++++++++++++++ .../js/{ => vue}/components/ProductCard.vue | 0 .../{ => vue}/components/ProductSection.vue | 0 .../js/{ => vue}/layouts/CheckoutLayout.vue | 0 resources/js/{ => vue}/pages/Checkout.vue | 0 resources/js/{ => vue}/pages/Plans.vue | 0 .../js/{ => vue}/pages/SettingsBilling.vue | 0 resources/js/{ => vue}/utils/intervals.ts | 0 9 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 resources/js/vue/app.ts rename resources/js/{ => vue}/components/ProductCard.vue (100%) rename resources/js/{ => vue}/components/ProductSection.vue (100%) rename resources/js/{ => vue}/layouts/CheckoutLayout.vue (100%) rename resources/js/{ => vue}/pages/Checkout.vue (100%) rename resources/js/{ => vue}/pages/Plans.vue (100%) rename resources/js/{ => vue}/pages/SettingsBilling.vue (100%) rename resources/js/{ => vue}/utils/intervals.ts (100%) diff --git a/resources/js/app.ts b/resources/js/app.ts index 5266312..3043f15 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -1,24 +1 @@ -import { registerIcon } from '@/lib/navigation'; -import IconCreditCard from '~icons/lucide/credit-card'; -import IconSparkles from '~icons/lucide/sparkles'; - -import '../css/style.css'; - -/** - * Billing module setup - * Called during app initialization before mounting - */ -export function setup() { - console.debug('Billing module loaded'); - - registerIcon('billing', IconCreditCard); - registerIcon('upgrade', IconSparkles); -} - -/** - * Billing module after mount logic - * Called after the app has been mounted - */ -export function afterMount() { - console.debug('Billing module after mount logic executed'); -} +export * from './vue/app'; diff --git a/resources/js/vue/app.ts b/resources/js/vue/app.ts new file mode 100644 index 0000000..855a355 --- /dev/null +++ b/resources/js/vue/app.ts @@ -0,0 +1,24 @@ +import { registerIcon } from '@/lib/navigation'; +import IconCreditCard from '~icons/lucide/credit-card'; +import IconSparkles from '~icons/lucide/sparkles'; + +import '@modules/billing/resources/css/style.css'; + +/** + * Billing module setup + * Called during app initialization before mounting + */ +export function setup() { + console.debug('Billing module loaded'); + + registerIcon('billing', IconCreditCard); + registerIcon('upgrade', IconSparkles); +} + +/** + * Billing module after mount logic + * Called after the app has been mounted + */ +export function afterMount() { + console.debug('Billing module after mount logic executed'); +} diff --git a/resources/js/components/ProductCard.vue b/resources/js/vue/components/ProductCard.vue similarity index 100% rename from resources/js/components/ProductCard.vue rename to resources/js/vue/components/ProductCard.vue diff --git a/resources/js/components/ProductSection.vue b/resources/js/vue/components/ProductSection.vue similarity index 100% rename from resources/js/components/ProductSection.vue rename to resources/js/vue/components/ProductSection.vue diff --git a/resources/js/layouts/CheckoutLayout.vue b/resources/js/vue/layouts/CheckoutLayout.vue similarity index 100% rename from resources/js/layouts/CheckoutLayout.vue rename to resources/js/vue/layouts/CheckoutLayout.vue diff --git a/resources/js/pages/Checkout.vue b/resources/js/vue/pages/Checkout.vue similarity index 100% rename from resources/js/pages/Checkout.vue rename to resources/js/vue/pages/Checkout.vue diff --git a/resources/js/pages/Plans.vue b/resources/js/vue/pages/Plans.vue similarity index 100% rename from resources/js/pages/Plans.vue rename to resources/js/vue/pages/Plans.vue diff --git a/resources/js/pages/SettingsBilling.vue b/resources/js/vue/pages/SettingsBilling.vue similarity index 100% rename from resources/js/pages/SettingsBilling.vue rename to resources/js/vue/pages/SettingsBilling.vue diff --git a/resources/js/utils/intervals.ts b/resources/js/vue/utils/intervals.ts similarity index 100% rename from resources/js/utils/intervals.ts rename to resources/js/vue/utils/intervals.ts From d20182e1a23b1ef5c3667cba2c8f763bddf96be0 Mon Sep 17 00:00:00 2001 From: roble Date: Wed, 27 May 2026 19:35:03 +0100 Subject: [PATCH 5/6] fix: Correct indentation for footer slot in ProductSection component --- resources/js/vue/components/ProductSection.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/vue/components/ProductSection.vue b/resources/js/vue/components/ProductSection.vue index 34dd0fb..e6fec19 100644 --- a/resources/js/vue/components/ProductSection.vue +++ b/resources/js/vue/components/ProductSection.vue @@ -110,6 +110,6 @@ function getToggleLabel(interval: string): string { /> - + From f63ddd7099af21d641267523083867b8c92df53a Mon Sep 17 00:00:00 2001 From: roble Date: Tue, 2 Jun 2026 20:04:50 +0100 Subject: [PATCH 6/6] fix: Update copyright year in LICENSE file to 2026 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f9a509b..cf78a69 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Saucebase +Copyright (c) 2026 Saucebase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal