From 9ad361e99268c9ba527ba1740d1c2279e2f8003a Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 16 May 2026 01:40:56 +0300 Subject: [PATCH] Update: 2026-05-16 01:40:56 --- app/modules_app/payments/review.php | 26 +++- .../controllers/subscription_controller.dart | 7 +- .../subscription/views/subscription_view.dart | 115 +++++++++++++++--- public/shell.php | 3 +- 4 files changed, 126 insertions(+), 25 deletions(-) diff --git a/app/modules_app/payments/review.php b/app/modules_app/payments/review.php index b3d48bb..42ce31d 100644 --- a/app/modules_app/payments/review.php +++ b/app/modules_app/payments/review.php @@ -47,18 +47,33 @@ try { $plan = $stmt->fetch(); if ($plan) { + $cycle = $payment['billing_cycle'] ?? 'annual'; $startDate = date('Y-m-d H:i:s'); - $endDate = date('Y-m-d H:i:s', strtotime('+30 days')); + + if ($cycle === 'monthly') { + $endDate = date('Y-m-d H:i:s', strtotime('+30 days')); + $maxInvoices = (int)$plan['max_invoices_month']; + $price = (float)($plan['price_monthly_jod'] ?? $plan['price_jod']); + } else { + $endDate = date('Y-m-d H:i:s', strtotime('+1 year')); + // Annual gets 12x the monthly quota + $maxInvoices = (int)($plan['max_invoices_month'] * 12); + $price = (float)($plan['price_annual_jod'] ?? ($plan['price_jod'] * 10)); + } $stmt = $db->prepare(" - INSERT INTO subscriptions (tenant_id, plan_id, max_companies, max_invoices_per_month, max_users, price_jod, status, current_period_start, current_period_end, updated_at) - VALUES (:t_id, :p_id, :max_c, :max_i, :max_u, :price, 'active', :start, :end, NOW()) + INSERT INTO subscriptions ( + tenant_id, plan_id, max_companies, max_invoices_per_month, max_users, + price_jod, billing_cycle, status, current_period_start, current_period_end, updated_at + ) + VALUES (:t_id, :p_id, :max_c, :max_i, :max_u, :price, :cycle, 'active', :start, :end, NOW()) ON DUPLICATE KEY UPDATE plan_id = VALUES(plan_id), max_companies = VALUES(max_companies), max_invoices_per_month = VALUES(max_invoices_per_month), max_users = VALUES(max_users), price_jod = VALUES(price_jod), + billing_cycle = VALUES(billing_cycle), status = 'active', current_period_start = VALUES(current_period_start), current_period_end = VALUES(current_period_end), @@ -68,9 +83,10 @@ try { 't_id' => $payment['tenant_id'], 'p_id' => $plan['id'], 'max_c' => $plan['max_companies'], - 'max_i' => $plan['max_invoices_month'], + 'max_i' => $maxInvoices, 'max_u' => $plan['max_users'], - 'price' => $plan['price_jod'], + 'price' => $price, + 'cycle' => $cycle, 'start' => $startDate, 'end' => $endDate ]); diff --git a/musadaq-app/lib/features/subscription/controllers/subscription_controller.dart b/musadaq-app/lib/features/subscription/controllers/subscription_controller.dart index e23b7ea..9158f2f 100644 --- a/musadaq-app/lib/features/subscription/controllers/subscription_controller.dart +++ b/musadaq-app/lib/features/subscription/controllers/subscription_controller.dart @@ -11,6 +11,7 @@ class SubscriptionController extends GetxController { var isLoading = true.obs; var isCreatingPayment = false.obs; var activePaymentRequest = Rxn>(); + var isAnnual = true.obs; // Toggle between Monthly and Annual @override void onInit() { @@ -69,7 +70,11 @@ class SubscriptionController extends GetxController { Future?> createPaymentRequest(String planId) async { try { isCreatingPayment.value = true; - final res = await DioClient().client.post('payments/create', data: {'plan_id': planId}); + final cycle = isAnnual.value ? 'annual' : 'monthly'; + final res = await DioClient().client.post('payments/create', data: { + 'plan_id': planId, + 'billing_cycle': cycle, + }); if (res.data['success'] == true && res.data['data'] != null) { final result = Map.from(res.data['data']); activePaymentRequest.value = result; diff --git a/musadaq-app/lib/features/subscription/views/subscription_view.dart b/musadaq-app/lib/features/subscription/views/subscription_view.dart index 127b61b..285bfa7 100644 --- a/musadaq-app/lib/features/subscription/views/subscription_view.dart +++ b/musadaq-app/lib/features/subscription/views/subscription_view.dart @@ -44,25 +44,93 @@ class SubscriptionView extends StatelessWidget { // Plans Header Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon(Icons.diamond_rounded, color: Color(0xFFD4AF37), size: 22), - const SizedBox(width: 8), - Text( - 'اختر باقتك', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: isDark ? Colors.white : const Color(0xFF0F172A), - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.diamond_rounded, color: Color(0xFFD4AF37), size: 22), + const SizedBox(width: 8), + Text( + 'اختر باقتك', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : const Color(0xFF0F172A), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'ادفع عبر CliQ — بدون عمولة!', + style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey), + ), + ], ), ], ), - const SizedBox(height: 4), - Text( - 'ادفع عبر CliQ — بدون عمولة!', - style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey), + const SizedBox(height: 20), + + // Toggle + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: isDark ? Colors.white.withOpacity(0.05) : Colors.black.withOpacity(0.05), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => controller.isAnnual.value = false, + child: Obx(() => Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: !controller.isAnnual.value ? const Color(0xFF0F4C81) : Colors.transparent, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: Text( + 'دفع شهري', + style: TextStyle( + color: !controller.isAnnual.value ? Colors.white : (isDark ? Colors.white60 : Colors.black54), + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + ), + )), + ), + ), + Expanded( + child: GestureDetector( + onTap: () => controller.isAnnual.value = true, + child: Obx(() => Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: controller.isAnnual.value ? const Color(0xFF0F4C81) : Colors.transparent, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: Text( + 'دفع سنوي (توفير ✨)', + style: TextStyle( + color: controller.isAnnual.value ? Colors.white : (isDark ? Colors.white60 : Colors.black54), + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + ), + )), + ), + ), + ], + ), ), - const SizedBox(height: 16), + const SizedBox(height: 20), // Plans Grid ...controller.plans.map((plan) => _buildPlanCard(plan, controller, isDark)), @@ -228,9 +296,19 @@ class SubscriptionView extends StatelessWidget { Widget _buildPlanCard(Map plan, SubscriptionController ctrl, bool isDark) { final isPopular = plan['is_popular'] == true; - final price = (plan['price_jod'] ?? 0).toString(); - final features = (plan['features'] as List?)?.cast() ?? []; final nameAr = plan['name_ar'] ?? plan['name_en'] ?? 'باقة'; + + return Obx(() { + final bool annual = ctrl.isAnnual.value; + final price = annual + ? (plan['price_annual_jod'] ?? (plan['price_jod'] * 10)).toString() + : (plan['price_monthly_jod'] ?? plan['price_jod']).toString(); + + final features = (plan['features'] as List?)?.cast() ?? []; + final invoiceLimit = annual + ? (plan['max_invoices_month'] * 12).toString() + : (plan['max_invoices_month']).toString(); + final cycleText = annual ? 'سنة' : 'شهر'; return Container( margin: const EdgeInsets.only(bottom: 16), @@ -271,7 +349,7 @@ class SubscriptionView extends StatelessWidget { text: TextSpan( children: [ TextSpan(text: price, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: isDark ? const Color(0xFF5EEAD4) : const Color(0xFF0F4C81))), - TextSpan(text: ' JOD', style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)), + TextSpan(text: ' JOD / $cycleText', style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)), ], ), ), @@ -302,7 +380,7 @@ class SubscriptionView extends StatelessWidget { children: [ _buildPlanStat(Icons.business, '${plan['max_companies'] ?? 0} شركات'), const SizedBox(width: 8), - _buildPlanStat(Icons.receipt_long, '${plan['max_invoices_month'] ?? 0} فاتورة/سنة'), + _buildPlanStat(Icons.receipt_long, '$invoiceLimit فاتورة/$cycleText'), const SizedBox(width: 8), _buildPlanStat(Icons.people, '${plan['max_users'] ?? 0} مستخدمين'), ], @@ -338,6 +416,7 @@ class SubscriptionView extends StatelessWidget { ], ), ); + }); } Widget _buildPlanStat(IconData icon, String text) { diff --git a/public/shell.php b/public/shell.php index c8722e7..1d9bdd4 100644 --- a/public/shell.php +++ b/public/shell.php @@ -1868,7 +1868,8 @@
- 📄 رصيد الفواتير (سنوي) + 📄 رصيد الفواتير +