Initial commit: Uruk Prize Platform architecture, backend core, mobile app, gateway caller & deployment pipeline
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Core\Request;
|
||||
use Core\Response;
|
||||
use Core\Security;
|
||||
use Core\Database;
|
||||
use App\Services\SuperQiParserService;
|
||||
use PDO;
|
||||
|
||||
class SubscriptionController
|
||||
{
|
||||
/**
|
||||
* GET /api/v1/subscription/plans
|
||||
*/
|
||||
public function getPlans(Request $request): void
|
||||
{
|
||||
$appConfig = require __DIR__ . '/../../config/app.php';
|
||||
|
||||
$plans = [
|
||||
[
|
||||
'id' => 'uruk-2year-standard',
|
||||
'name' => 'عضوية جائزة أوروك التنفيذية (سنتان)',
|
||||
'duration_years' => 2,
|
||||
'price_usd' => (float)$appConfig['membership_price_usd'],
|
||||
'price_iqd' => (float)$appConfig['membership_price_iqd'],
|
||||
'benefits' => [
|
||||
'خصم 50% في شبكة المستشفيات والمراكز التخصصية في العراق والأردن ومصر ولبنان',
|
||||
'خصومات فندقية وسياحية تتراوح بين 40% و60% في أرقى الفنادق الشريكة',
|
||||
'وصول مجاني لأكثر من 30 تخصصاً تدريبياً معتمداً حضورياً وعبر المنصة الرقمية',
|
||||
'شهادات معتمدة ومسجلة رسمياً باسم المشترك',
|
||||
'هوية رقمية ذكية مع رمز QR مشفر للتحقق الفوري',
|
||||
],
|
||||
'official_activation_date' => '2026-01-01',
|
||||
'early_registration_active' => true,
|
||||
],
|
||||
];
|
||||
|
||||
Response::success($plans, 'Available subscription plans retrieved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/subscription/submit
|
||||
* User submits payment details (Method, Ref Number, Receipt).
|
||||
*/
|
||||
public function submitSubscription(Request $request): void
|
||||
{
|
||||
$userId = (int)$request->getHeader('x-user-id');
|
||||
$method = strtoupper(trim((string)$request->get('method', 'SUPER_QI')));
|
||||
$refNumber = trim((string)$request->get('reference_number'));
|
||||
$receiptUrl = trim((string)$request->get('receipt_url', ''));
|
||||
|
||||
if (empty($refNumber)) {
|
||||
Response::error('Payment transaction reference number is required.');
|
||||
}
|
||||
|
||||
$appConfig = require __DIR__ . '/../../config/app.php';
|
||||
$pdo = Database::getConnection();
|
||||
|
||||
// 1. Generate unique membership number (e.g. URUK-2026-XXXX)
|
||||
$memNumber = 'URUK-' . date('Y') . '-' . strtoupper(substr(uniqid(), -5));
|
||||
$qrSeed = Security::generateRandomHex(16);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
// Create subscription record with PENDING_PAYMENT
|
||||
$subStmt = $pdo->prepare('
|
||||
INSERT INTO subscriptions (user_id, membership_number, plan_name, price_usd, price_local, currency, starts_at, expires_at, status, qr_seed, created_at)
|
||||
VALUES (:uid, :mem, "2-Year Executive Membership", :usd, :iqd, "IQD", CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 YEAR), "PENDING_PAYMENT", :seed, NOW())
|
||||
');
|
||||
$subStmt->execute([
|
||||
':uid' => $userId,
|
||||
':mem' => $memNumber,
|
||||
':usd' => $appConfig['membership_price_usd'],
|
||||
':iqd' => $appConfig['membership_price_iqd'],
|
||||
':seed' => $qrSeed,
|
||||
]);
|
||||
$subscriptionId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Create transaction record
|
||||
$transStmt = $pdo->prepare('
|
||||
INSERT INTO transactions (subscription_id, user_id, method, reference_number, amount, currency, status, receipt_image_url, created_at)
|
||||
VALUES (:sub_id, :uid, :method, :ref, :amount, "IQD", "SUBMITTED", :receipt, NOW())
|
||||
');
|
||||
$transStmt->execute([
|
||||
':sub_id' => $subscriptionId,
|
||||
':uid' => $userId,
|
||||
':method' => $method,
|
||||
':ref' => $refNumber,
|
||||
':amount' => $appConfig['membership_price_iqd'],
|
||||
':receipt' => $receiptUrl,
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// 2. Check if a matching SuperQi / ZainCash notification was already ingested by the Android listener
|
||||
$checkStmt = $pdo->prepare('
|
||||
SELECT id, amount, status FROM transactions
|
||||
WHERE reference_number = :ref AND id != :current_id AND status = "VERIFIED_AUTO"
|
||||
LIMIT 1
|
||||
');
|
||||
$checkStmt->execute([':ref' => $refNumber, ':current_id' => (int)$pdo->lastInsertId()]);
|
||||
$preExistingPayment = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($preExistingPayment) {
|
||||
// Instantly activate!
|
||||
$actSub = $pdo->prepare('UPDATE subscriptions SET status = "ACTIVE" WHERE id = :id');
|
||||
$actSub->execute([':id' => $subscriptionId]);
|
||||
|
||||
$actTrans = $pdo->prepare('UPDATE transactions SET status = "VERIFIED_AUTO", verified_at = NOW() WHERE subscription_id = :id');
|
||||
$actTrans->execute([':id' => $subscriptionId]);
|
||||
|
||||
Response::success([
|
||||
'subscription_id' => $subscriptionId,
|
||||
'membership_number' => $memNumber,
|
||||
'status' => 'ACTIVE',
|
||||
'message' => 'تم التحقق من الحوالة آلياً بنجاح وتفعيل العضوية فورياً!',
|
||||
], 'Membership activated automatically via SuperQi verification.');
|
||||
}
|
||||
|
||||
Response::success([
|
||||
'subscription_id' => $subscriptionId,
|
||||
'membership_number' => $memNumber,
|
||||
'status' => 'PENDING_PAYMENT',
|
||||
'message' => 'تم استلام طلبك بنجاح وجارٍ التحقق من الحوالة وتفعيل بطاقتك.',
|
||||
], 'Subscription request registered. Verification in progress.');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
Response::error('Failed to submit subscription: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/subscription/my-card
|
||||
*/
|
||||
public function getMyCard(Request $request): void
|
||||
{
|
||||
$userId = (int)$request->getHeader('x-user-id');
|
||||
$pdo = Database::getConnection();
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT s.id, s.membership_number, s.plan_name, s.status, s.starts_at, s.expires_at,
|
||||
u.full_name, u.phone, u.avatar_url
|
||||
FROM subscriptions s
|
||||
JOIN users u ON u.id = s.user_id
|
||||
WHERE s.user_id = :uid
|
||||
ORDER BY s.id DESC
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute([':uid' => $userId]);
|
||||
$card = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$card) {
|
||||
Response::notFound('No subscription card found for this user.');
|
||||
}
|
||||
|
||||
Response::success($card, 'Digital card details retrieved.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user