Update: 2026-05-16 01:36:22
This commit is contained in:
@@ -31,7 +31,7 @@ final class QuotaMiddleware
|
||||
|
||||
// Fetch subscription with plan info
|
||||
$stmt = $db->prepare("
|
||||
SELECT s.*, sp.name_ar as plan_name, sp.ai_features, sp.jofotara_enabled
|
||||
SELECT s.*, sp.name_ar as plan_name, sp.ai_features, sp.jofotara_enabled, sp.price_monthly_jod, sp.price_annual_jod
|
||||
FROM subscriptions s
|
||||
LEFT JOIN subscription_plans sp ON s.plan_id = sp.id
|
||||
WHERE s.tenant_id = ?
|
||||
@@ -60,7 +60,9 @@ final class QuotaMiddleware
|
||||
// Auto-reset period counter if billing period has ended
|
||||
if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) {
|
||||
$newStart = date('Y-m-d H:i:s');
|
||||
$newEnd = date('Y-m-d H:i:s', strtotime('+1 year')); // Changed to annual
|
||||
$cycle = $sub['billing_cycle'] ?? 'annual';
|
||||
$interval = ($cycle === 'monthly') ? '+1 month' : '+1 year';
|
||||
$newEnd = date('Y-m-d H:i:s', strtotime($interval));
|
||||
|
||||
$resetStmt = $db->prepare("
|
||||
UPDATE subscriptions
|
||||
|
||||
@@ -15,6 +15,8 @@ return [
|
||||
'max_invoices_month' => 15,
|
||||
'max_users' => 1,
|
||||
'price_jod' => 0.00,
|
||||
'price_monthly_jod' => 0.00,
|
||||
'price_annual_jod' => 0.00,
|
||||
'ai_features' => true,
|
||||
'jofotara_enabled' => true,
|
||||
'badge_color' => 'gray',
|
||||
@@ -29,43 +31,47 @@ return [
|
||||
],
|
||||
'basic' => [
|
||||
'id' => 'basic',
|
||||
'name_ar' => 'الباقة الأساسية (سنوي)',
|
||||
'name_en' => 'Basic Plan (Annual)',
|
||||
'max_companies' => 1,
|
||||
'max_invoices_month' => 12000,
|
||||
'max_users' => 1,
|
||||
'price_jod' => 120.00,
|
||||
'name_ar' => 'الباقة الأساسية',
|
||||
'name_en' => 'Basic Plan',
|
||||
'max_companies' => 3,
|
||||
'max_invoices_month' => 500,
|
||||
'max_users' => 2,
|
||||
'price_jod' => 15.00, // Default legacy price
|
||||
'price_monthly_jod' => 15.00,
|
||||
'price_annual_jod' => 120.00,
|
||||
'ai_features' => true,
|
||||
'jofotara_enabled' => true,
|
||||
'badge_color' => 'blue',
|
||||
'description_ar' => 'للمحاسبين المستقلين والشركات الصغيرة — 12,000 فاتورة سنوياً',
|
||||
'description_ar' => 'للمحاسبين المستقلين والشركات الصغيرة — 3 شركات',
|
||||
'features' => [
|
||||
'استخراج الفواتير بالذكاء الاصطناعي',
|
||||
'الربط المباشر مع جوفوترة',
|
||||
'شركة واحدة فقط',
|
||||
'12,000 فاتورة سنوياً (سخية جداً)',
|
||||
'مستخدم واحد',
|
||||
'حتى 3 شركات (بدلاً من واحدة)',
|
||||
'500 فاتورة شهرياً (سخية جداً)',
|
||||
'مستخدمين اثنين',
|
||||
'دعم فني عبر الواتساب',
|
||||
],
|
||||
],
|
||||
'pro' => [
|
||||
'id' => 'pro',
|
||||
'name_ar' => 'الباقة الاحترافية (سنوي)',
|
||||
'name_en' => 'Pro Plan (Annual)',
|
||||
'name_ar' => 'الباقة الاحترافية',
|
||||
'name_en' => 'Pro Plan',
|
||||
'max_companies' => 9999,
|
||||
'max_invoices_month' => 50000,
|
||||
'max_invoices_month' => 3000,
|
||||
'max_users' => 5,
|
||||
'price_jod' => 250.00,
|
||||
'price_jod' => 35.00, // Default legacy price
|
||||
'price_monthly_jod' => 35.00,
|
||||
'price_annual_jod' => 290.00,
|
||||
'ai_features' => true,
|
||||
'jofotara_enabled' => true,
|
||||
'badge_color' => 'gold',
|
||||
'is_popular' => true,
|
||||
'description_ar' => 'للمكاتب الكبيرة والموزعين — 50,000 فاتورة سنوياً',
|
||||
'description_ar' => 'للمكاتب الكبيرة والموزعين — حجم عمل ضخم',
|
||||
'features' => [
|
||||
'استخراج الفواتير بالذكاء الاصطناعي',
|
||||
'الربط المباشر مع جوفوترة',
|
||||
'عدد شركات غير محدود',
|
||||
'50,000 فاتورة سنوياً',
|
||||
'3,000 فاتورة شهرياً',
|
||||
'5 مستخدمين',
|
||||
'API كامل لتطبيق الهاتف',
|
||||
'مدير حساب مخصص',
|
||||
|
||||
@@ -31,7 +31,7 @@ final class QuotaMiddleware
|
||||
|
||||
// Fetch subscription with plan info
|
||||
$stmt = $db->prepare("
|
||||
SELECT s.*, sp.name_ar as plan_name, sp.ai_features, sp.jofotara_enabled
|
||||
SELECT s.*, sp.name_ar as plan_name, sp.ai_features, sp.jofotara_enabled, sp.price_monthly_jod, sp.price_annual_jod
|
||||
FROM subscriptions s
|
||||
LEFT JOIN subscription_plans sp ON s.plan_id = sp.id
|
||||
WHERE s.tenant_id = ?
|
||||
@@ -60,7 +60,9 @@ final class QuotaMiddleware
|
||||
// Auto-reset period counter if billing period has ended
|
||||
if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) {
|
||||
$newStart = date('Y-m-d H:i:s');
|
||||
$newEnd = date('Y-m-d H:i:s', strtotime('+1 year')); // Changed to annual
|
||||
$cycle = $sub['billing_cycle'] ?? 'annual';
|
||||
$interval = ($cycle === 'monthly') ? '+1 month' : '+1 year';
|
||||
$newEnd = date('Y-m-d H:i:s', strtotime($interval));
|
||||
|
||||
$resetStmt = $db->prepare("
|
||||
UPDATE subscriptions
|
||||
|
||||
@@ -32,8 +32,13 @@ if ($errors) {
|
||||
|
||||
$db = Database::getInstance();
|
||||
$tenantId = $decoded['tenant_id'];
|
||||
$userId = $decoded['user_id'];
|
||||
$planId = $data['plan_id'];
|
||||
$userId = $decoded['user_id'];
|
||||
$planId = $data['plan_id'];
|
||||
$cycle = $data['billing_cycle'] ?? 'annual'; // Default to annual
|
||||
|
||||
if (!in_array($cycle, ['monthly', 'annual'])) {
|
||||
json_error('دورة الفوترة غير صالحة.', 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Get plan details
|
||||
@@ -45,6 +50,9 @@ try {
|
||||
json_error('الباقة المختارة غير صالحة أو غير نشطة.', 422);
|
||||
}
|
||||
|
||||
// Determine amount based on cycle
|
||||
$amount = ($cycle === 'monthly') ? ($plan['price_monthly_jod'] ?? $plan['price_jod']) : ($plan['price_annual_jod'] ?? ($plan['price_jod'] * 10));
|
||||
|
||||
// 2. Check for existing pending payment for this tenant
|
||||
$stmt = $db->prepare("SELECT id FROM payment_requests WHERE tenant_id = ? AND status = 'pending' LIMIT 1");
|
||||
$stmt->execute([$tenantId]);
|
||||
@@ -68,15 +76,16 @@ try {
|
||||
// 6. Create payment request
|
||||
$paymentId = Database::generateUuid();
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO payment_requests (id, tenant_id, user_id, plan_id, amount_jod, internal_reference, cliq_alias, payer_name, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW())
|
||||
INSERT INTO payment_requests (id, tenant_id, user_id, plan_id, billing_cycle, amount_jod, internal_reference, cliq_alias, payer_name, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
$paymentId,
|
||||
$tenantId,
|
||||
$userId,
|
||||
$planId,
|
||||
$plan['price_jod'],
|
||||
$cycle,
|
||||
$amount,
|
||||
$referenceNumber,
|
||||
$cliqAlias,
|
||||
$user['name'] ?? ''
|
||||
@@ -88,17 +97,17 @@ try {
|
||||
$tenantId,
|
||||
$userId,
|
||||
$paymentId,
|
||||
json_encode(['plan_id' => $planId, 'amount' => $plan['price_jod'], 'ref' => $referenceNumber])
|
||||
json_encode(['plan_id' => $planId, 'cycle' => $cycle, 'amount' => $amount, 'ref' => $referenceNumber])
|
||||
]);
|
||||
|
||||
json_success([
|
||||
'payment_id' => $paymentId,
|
||||
'reference_number' => $referenceNumber,
|
||||
'cliq_alias' => $cliqAlias,
|
||||
'amount_jod' => (float)$plan['price_jod'],
|
||||
'plan_name' => $plan['name_ar'] ?? $plan['name_en'],
|
||||
'amount_jod' => (float)$amount,
|
||||
'plan_name' => ($plan['name_ar'] ?? $plan['name_en']) . " (" . ($cycle === 'monthly' ? 'شهري' : 'سنوي') . ")",
|
||||
'payer_name' => $user['name'] ?? '',
|
||||
'instructions' => "قم بالتحويل عبر CliQ إلى الاسم المستعار: {$cliqAlias} بمبلغ {$plan['price_jod']} دينار أردني.",
|
||||
'instructions' => "قم بالتحويل عبر CliQ إلى الاسم المستعار: {$cliqAlias} بمبلغ {$amount} دينار أردني.",
|
||||
], 'تم إنشاء طلب الدفع بنجاح');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -13,7 +13,7 @@ $db = Database::getInstance();
|
||||
try {
|
||||
$stmt = $db->query("
|
||||
SELECT id, name_ar, name_en, max_companies, max_invoices_month, max_users,
|
||||
price_jod, ai_features, jofotara_enabled, sort_order
|
||||
price_jod, price_annual_jod, price_monthly_jod, ai_features, jofotara_enabled, sort_order
|
||||
FROM subscription_plans
|
||||
WHERE is_active = 1
|
||||
ORDER BY sort_order ASC
|
||||
@@ -36,6 +36,8 @@ try {
|
||||
$plan['max_invoices_month'] = (int)$plan['max_invoices_month'];
|
||||
$plan['max_users'] = (int)$plan['max_users'];
|
||||
$plan['price_jod'] = (float)$plan['price_jod'];
|
||||
$plan['price_annual_jod'] = (float)$plan['price_annual_jod'];
|
||||
$plan['price_monthly_jod'] = (float)$plan['price_monthly_jod'];
|
||||
$plan['ai_features'] = (bool)$plan['ai_features'];
|
||||
$plan['jofotara_enabled'] = (bool)$plan['jofotara_enabled'];
|
||||
}
|
||||
|
||||
@@ -164,11 +164,23 @@
|
||||
<h2>اختر الباقة المناسبة لحجم أعمالك</h2>
|
||||
<p>لا رسوم خفية. لا عقود طويلة. ابدأ مجاناً وتدرّج حسب احتياجك.</p>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:center; margin-bottom:40px;">
|
||||
<div class="cycle-toggle" style="background:rgba(255,255,255,0.05); padding:6px; border-radius:14px; display:inline-flex; border:1px solid rgba(255,255,255,0.1); cursor:pointer;" onclick="this.classList.toggle('monthly'); document.querySelectorAll('.price-monthly').forEach(el=>el.style.display=this.classList.contains('monthly')?'block':'none'); document.querySelectorAll('.price-annual').forEach(el=>el.style.display=this.classList.contains('monthly')?'none':'block');">
|
||||
<span class="toggle-btn annual-btn" style="padding:10px 24px; border-radius:10px; font-size:14px; font-weight:700; color:white; background:var(--green-mid); transition:all 0.3s;">دفع سنوي (توفير ✨)</span>
|
||||
<span class="toggle-btn monthly-btn" style="padding:10px 24px; border-radius:10px; font-size:14px; font-weight:700; color:var(--text-3); transition:all 0.3s;">دفع شهري</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.cycle-toggle.monthly .annual-btn { background:transparent; color:var(--text-3); }
|
||||
.cycle-toggle.monthly .monthly-btn { background:var(--green-mid); color:white; }
|
||||
</style>
|
||||
|
||||
<div class="pricing-grid">
|
||||
|
||||
<div class="price-card">
|
||||
<div class="price-name">التجربة المجانية</div>
|
||||
<div class="price-amount">0 <span>دينار/سنة</span></div>
|
||||
<div class="price-amount">0 <span>دينار/شهر</span></div>
|
||||
<div class="price-desc">للتجربة الأولية</div>
|
||||
<ul class="price-features">
|
||||
<li><span class="feature-check">✔</span> شركة واحدة</li>
|
||||
@@ -182,13 +194,14 @@
|
||||
|
||||
<div class="price-card popular">
|
||||
<div class="popular-badge">⭐ الأكثر اختياراً</div>
|
||||
<div class="price-name">الباقة الأساسية (سنوي)</div>
|
||||
<div class="price-amount">120 <span>دينار/سنة</span></div>
|
||||
<div class="price-name">الباقة الأساسية</div>
|
||||
<div class="price-amount price-annual">120 <span>دينار/سنة</span></div>
|
||||
<div class="price-amount price-monthly" style="display:none;">15 <span>دينار/شهر</span></div>
|
||||
<div class="price-desc">للمحاسبين والشركات الصغيرة</div>
|
||||
<ul class="price-features">
|
||||
<li><span class="feature-check">✔</span> شركة واحدة</li>
|
||||
<li><span class="feature-check">✔</span> 12,000 فاتورة سنوياً</li>
|
||||
<li><span class="feature-check">✔</span> مستخدم واحد</li>
|
||||
<li><span class="feature-check">✔</span> حتى 3 شركات</li>
|
||||
<li><span class="feature-check">✔</span> 500 فاتورة شهرياً</li>
|
||||
<li><span class="feature-check">✔</span> مستخدمين اثنين</li>
|
||||
<li><span class="feature-check">✔</span> دعم فني متكامل</li>
|
||||
<li><span class="feature-check">✔</span> ربط مباشر مع جوفوترة</li>
|
||||
</ul>
|
||||
@@ -196,12 +209,13 @@
|
||||
</div>
|
||||
|
||||
<div class="price-card">
|
||||
<div class="price-name">الباقة الاحترافية (سنوي)</div>
|
||||
<div class="price-amount">250 <span>دينار/سنة</span></div>
|
||||
<div class="price-name">الباقة الاحترافية</div>
|
||||
<div class="price-amount price-annual">290 <span>دينار/سنة</span></div>
|
||||
<div class="price-amount price-monthly" style="display:none;">35 <span>دينار/شهر</span></div>
|
||||
<div class="price-desc">للمكاتب الكبيرة والموزعين</div>
|
||||
<ul class="price-features">
|
||||
<li><span class="feature-check">✔</span> شركات غير محدودة</li>
|
||||
<li><span class="feature-check">✔</span> 50,000 فاتورة سنوياً</li>
|
||||
<li><span class="feature-check">✔</span> 3,000 فاتورة شهرياً</li>
|
||||
<li><span class="feature-check">✔</span> 5 مستخدمين</li>
|
||||
<li><span class="feature-check">✔</span> تدقيق ذكي استباقي</li>
|
||||
<li><span class="feature-check">✔</span> مدير حساب مخصص</li>
|
||||
|
||||
@@ -1951,6 +1951,22 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cycle Toggle -->
|
||||
<div style="display:flex; justify-content:center; margin-bottom:36px;">
|
||||
<div style="background:var(--bg-secondary); padding:5px; border-radius:12px; display:flex; gap:5px; border:1px solid var(--border);">
|
||||
<button @click="billingCycle = 'monthly'"
|
||||
:class="billingCycle === 'monthly' ? 'btn-navy' : 'btn-ghost'"
|
||||
style="font-size:13px; padding:8px 20px; border-radius:8px; transition:all 0.2s;">
|
||||
دفع شهري
|
||||
</button>
|
||||
<button @click="billingCycle = 'annual'"
|
||||
:class="billingCycle === 'annual' ? 'btn-navy' : 'btn-ghost'"
|
||||
style="font-size:13px; padding:8px 20px; border-radius:8px; transition:all 0.2s;">
|
||||
دفع سنوي (توفير ✨)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(290px, 1fr)); gap:24px;">
|
||||
<template x-for="p in plans" :key="p.id">
|
||||
<div class="plan-card" :class="subscription?.plan_id === p.id ? 'active-plan' : ''">
|
||||
@@ -1969,9 +1985,9 @@
|
||||
style="text-align:center; padding:18px 0; border-top:1px solid var(--border); border-bottom:1px solid var(--border);">
|
||||
<span class="num-font"
|
||||
style="font-size:46px; font-weight:800; color:var(--green-mid);"
|
||||
x-text="p.price_jod"></span>
|
||||
x-text="billingCycle === 'monthly' ? (p.price_monthly_jod || p.price_jod) : (p.price_annual_jod || (p.price_jod * 10))"></span>
|
||||
<span style="font-size:15px; color:var(--text-3); font-weight:500;"> دينار /
|
||||
سنة</span>
|
||||
<span x-text="billingCycle === 'monthly' ? 'شهر' : 'سنة'"></span></span>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
@@ -2927,7 +2943,9 @@
|
||||
isUploadingBatch: false, batchProgress: { total: 0, current: 0 },
|
||||
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
|
||||
acknowledgedWarnings: false, isEditingInvoice: false,
|
||||
isBusy: false, globalError: '',
|
||||
isBusy: false,
|
||||
billingCycle: 'annual', // 'monthly' or 'annual'
|
||||
globalError: '',
|
||||
|
||||
newUser: { name: '', email: '', password: '', role: 'accountant', tenant_id: '' },
|
||||
newCompany: { name: '', tax_identification_number: '', commercial_registration_number: '', address: '', tenant_id: '' },
|
||||
@@ -3399,7 +3417,10 @@
|
||||
const res = await fetch('/index.php?route=v1/payments/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ plan_id: plan.id })
|
||||
body: JSON.stringify({
|
||||
plan_id: plan.id,
|
||||
billing_cycle: this.billingCycle
|
||||
})
|
||||
});
|
||||
const json = await res.json();
|
||||
this.isBusy = false;
|
||||
|
||||
46
update_subscription_strategy.sql
Normal file
46
update_subscription_strategy.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- 1. إضافة أعمدة الأسعار (شهري وسنوي) وجدولة الفواتير
|
||||
ALTER TABLE subscription_plans
|
||||
ADD COLUMN price_annual_jod DECIMAL(10,2) DEFAULT 0.00 AFTER price_jod,
|
||||
ADD COLUMN price_monthly_jod DECIMAL(10,2) DEFAULT 0.00 AFTER price_annual_jod;
|
||||
|
||||
-- 2. إضافة دورة الفوترة لجدول الاشتراكات وطلبات الدفع
|
||||
ALTER TABLE subscriptions
|
||||
ADD COLUMN billing_cycle ENUM('monthly', 'annual') DEFAULT 'annual' AFTER status;
|
||||
|
||||
ALTER TABLE payment_requests
|
||||
ADD COLUMN billing_cycle ENUM('monthly', 'annual') DEFAULT 'annual' AFTER plan_id;
|
||||
|
||||
-- 3. تحديث الباقات بالقيم الجديدة المقترحة في التحليل الاستراتيجي
|
||||
-- الباقة الأساسية
|
||||
UPDATE subscription_plans
|
||||
SET
|
||||
name_ar = 'الباقة الأساسية',
|
||||
price_annual_jod = 120.00,
|
||||
price_monthly_jod = 15.00,
|
||||
max_invoices_month = 500, -- تم تخفيضها من 12000 للتحويل المستقبلي
|
||||
max_companies = 3, -- تم زيادتها من 1 لجذب المحاسبين المستقلين
|
||||
max_users = 2,
|
||||
is_active = 1
|
||||
WHERE id = 'basic';
|
||||
|
||||
-- الباقة الاحترافية
|
||||
UPDATE subscription_plans
|
||||
SET
|
||||
name_ar = 'الباقة الاحترافية',
|
||||
price_annual_jod = 290.00,
|
||||
price_monthly_jod = 35.00,
|
||||
max_invoices_month = 3000,
|
||||
max_companies = 9999,
|
||||
max_users = 5,
|
||||
is_active = 1
|
||||
WHERE id = 'pro';
|
||||
|
||||
-- الباقة المجانية
|
||||
UPDATE subscription_plans
|
||||
SET
|
||||
price_annual_jod = 0.00,
|
||||
price_monthly_jod = 0.00,
|
||||
max_invoices_month = 15,
|
||||
max_companies = 1,
|
||||
max_users = 1
|
||||
WHERE id = 'free';
|
||||
Reference in New Issue
Block a user