feat: implement annual subscription model across backend quota system and flutter UI

This commit is contained in:
Hamza-Ayed
2026-05-16 00:15:38 +03:00
parent bddee7ca2d
commit e93f1d4f34
5 changed files with 63 additions and 11 deletions

View File

@@ -57,10 +57,10 @@ final class QuotaMiddleware
json_error('اشتراكك متأخر الدفع. يرجى تسوية المبلغ المستحق للمتابعة.', 403); json_error('اشتراكك متأخر الدفع. يرجى تسوية المبلغ المستحق للمتابعة.', 403);
} }
// Auto-reset monthly counter if billing period has ended // Auto-reset period counter if billing period has ended
if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) { if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) {
$newStart = date('Y-m-d H:i:s'); $newStart = date('Y-m-d H:i:s');
$newEnd = date('Y-m-d H:i:s', strtotime('+30 days')); $newEnd = date('Y-m-d H:i:s', strtotime('+1 year')); // Changed to annual
$resetStmt = $db->prepare(" $resetStmt = $db->prepare("
UPDATE subscriptions UPDATE subscriptions
@@ -76,15 +76,15 @@ final class QuotaMiddleware
$sub['current_period_start'] = $newStart; $sub['current_period_start'] = $newStart;
$sub['current_period_end'] = $newEnd; $sub['current_period_end'] = $newEnd;
error_log("QuotaMiddleware: Auto-reset monthly counter for tenant {$tenantId}"); error_log("QuotaMiddleware: Auto-reset annual counter for tenant {$tenantId}");
} }
// Check invoice quota // Check invoice quota
$used = (int)$sub['invoices_used_this_month']; $used = (int)$sub['invoices_used_this_month'];
$limit = (int)$sub['max_invoices_per_month']; $limit = (int)$sub['max_invoices_per_month']; // Keeping the DB column name the same for compatibility
if ($used >= $limit) { if ($used >= $limit) {
json_error('لقد وصلت للحد الأقصى من الفواتير المسموحة هذا الشهر (' . $limit . ' فاتورة). يرجى ترقية باقتك.', 429, [ json_error('لقد وصلت للحد الأقصى من الفواتير المسموحة في باقتك الحالية (' . $limit . ' فاتورة). يرجى ترقية باقتك للاستمرار.', 429, [
'quota_type' => 'invoices', 'quota_type' => 'invoices',
'used' => $used, 'used' => $used,
'limit' => $limit, 'limit' => $limit,

View File

@@ -57,10 +57,10 @@ final class QuotaMiddleware
json_error('اشتراكك متأخر الدفع. يرجى تسوية المبلغ المستحق للمتابعة.', 403); json_error('اشتراكك متأخر الدفع. يرجى تسوية المبلغ المستحق للمتابعة.', 403);
} }
// Auto-reset monthly counter if billing period has ended // Auto-reset period counter if billing period has ended
if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) { if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) {
$newStart = date('Y-m-d H:i:s'); $newStart = date('Y-m-d H:i:s');
$newEnd = date('Y-m-d H:i:s', strtotime('+30 days')); $newEnd = date('Y-m-d H:i:s', strtotime('+1 year')); // Changed to annual
$resetStmt = $db->prepare(" $resetStmt = $db->prepare("
UPDATE subscriptions UPDATE subscriptions
@@ -76,15 +76,15 @@ final class QuotaMiddleware
$sub['current_period_start'] = $newStart; $sub['current_period_start'] = $newStart;
$sub['current_period_end'] = $newEnd; $sub['current_period_end'] = $newEnd;
error_log("QuotaMiddleware: Auto-reset monthly counter for tenant {$tenantId}"); error_log("QuotaMiddleware: Auto-reset annual counter for tenant {$tenantId}");
} }
// Check invoice quota // Check invoice quota
$used = (int)$sub['invoices_used_this_month']; $used = (int)$sub['invoices_used_this_month'];
$limit = (int)$sub['max_invoices_per_month']; $limit = (int)$sub['max_invoices_per_month']; // Keeping the DB column name the same for compatibility
if ($used >= $limit) { if ($used >= $limit) {
json_error('لقد وصلت للحد الأقصى من الفواتير المسموحة هذا الشهر (' . $limit . ' فاتورة). يرجى ترقية باقتك.', 429, [ json_error('لقد وصلت للحد الأقصى من الفواتير المسموحة في باقتك الحالية (' . $limit . ' فاتورة). يرجى ترقية باقتك للاستمرار.', 429, [
'quota_type' => 'invoices', 'quota_type' => 'invoices',
'used' => $used, 'used' => $used,
'limit' => $limit, 'limit' => $limit,

View File

@@ -302,7 +302,7 @@ class SubscriptionView extends StatelessWidget {
children: [ children: [
_buildPlanStat(Icons.business, '${plan['max_companies'] ?? 0} شركات'), _buildPlanStat(Icons.business, '${plan['max_companies'] ?? 0} شركات'),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildPlanStat(Icons.receipt_long, '${plan['max_invoices_month'] ?? 0} فاتورة/شهر'), _buildPlanStat(Icons.receipt_long, '${plan['max_invoices_month'] ?? 0} فاتورة/سنة'),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildPlanStat(Icons.people, '${plan['max_users'] ?? 0} مستخدمين'), _buildPlanStat(Icons.people, '${plan['max_users'] ?? 0} مستخدمين'),
], ],

46
update_annual_plans.sql Normal file
View File

@@ -0,0 +1,46 @@
-- 1. Update existing plans to annual quotas and pricing
-- We'll assume the basic plan ID is 'basic' and pro is 'pro'. If they are different, they will need adjusting.
-- Basic Plan (Annual)
UPDATE subscription_plans
SET
name_ar = 'الباقة الأساسية (سنوي)',
name_en = 'Basic Plan (Annual)',
price = 120.00,
max_invoices_per_month = 12000,
max_companies = 1,
max_users = 1
WHERE id = 'basic';
-- Pro Plan (Annual)
UPDATE subscription_plans
SET
name_ar = 'الباقة الاحترافية (سنوي)',
name_en = 'Pro Plan (Annual)',
price = 250.00,
max_invoices_per_month = 50000,
max_companies = 9999, -- unlimited
max_users = 5
WHERE id = 'pro';
-- Free Trial Plan
UPDATE subscription_plans
SET
name_ar = 'التجربة المجانية',
name_en = 'Free Trial',
price = 0.00,
max_invoices_per_month = 15,
max_companies = 1,
max_users = 1
WHERE id = 'free';
-- 2. Update existing active subscriptions to match the new annual quota limits so no one gets blocked
UPDATE subscriptions s
JOIN subscription_plans sp ON s.plan_id = sp.id
SET
s.max_invoices_per_month = sp.max_invoices_per_month,
s.max_companies = sp.max_companies,
s.max_users = sp.max_users,
-- Adjust the period end to +1 year if it's currently set to a month (for paid plans only)
s.current_period_end = IF(s.plan_id != 'free', DATE_ADD(s.current_period_start, INTERVAL 1 YEAR), s.current_period_end);

6
update_plans.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
require_once __DIR__ . '/app/bootstrap/init.php';
use App\Core\Database;
$db = Database::getInstance();
$plans = $db->query("SELECT * FROM subscription_plans")->fetchAll();
print_r($plans);