Update: 2026-05-16 01:51:22

This commit is contained in:
Hamza-Ayed
2026-05-16 01:51:22 +03:00
parent 6e4dbf25ba
commit dec472dea9
7 changed files with 550 additions and 166 deletions

View File

@@ -1,37 +1,56 @@
package com.jordanbot.autoride
import android.os.Bundle
import android.widget.Button
import android.view.View
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.jordanbot.autoride.api.ActivateSubscriptionRequest
import com.jordanbot.autoride.api.ApiClient
import com.jordanbot.autoride.api.CheckPaymentRequest
import com.jordanbot.autoride.api.InitPaymentRequest
import com.jordanbot.autoride.subscription.SubscriptionManager
import com.jordanbot.autoride.utils.DeviceUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
class SubscriptionActivity : AppCompatActivity() {
private lateinit var tvStatus: TextView
private lateinit var btnBasic: Button
private lateinit var btnPro: Button
private lateinit var btnAnnual: Button
// Payment Overlay UI
private lateinit var layoutOverlay: LinearLayout
private lateinit var tvRefCode: TextView
private lateinit var tvTimer: TextView
private lateinit var btnCancel: Button
private var pollingJob: Job? = null
private var timerJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_subscription)
tvStatus = findViewById(R.id.tv_current_status)
btnBasic = findViewById(R.id.btn_subscribe_basic)
btnPro = findViewById(R.id.btn_subscribe_pro)
btnAnnual = findViewById(R.id.btn_subscribe_annual)
layoutOverlay = findViewById(R.id.layout_payment_overlay)
tvRefCode = findViewById(R.id.tv_ref_code)
tvTimer = findViewById(R.id.tv_timer)
btnCancel = findViewById(R.id.btn_cancel_payment)
updateStatusUI()
btnBasic.setOnClickListener { activatePlan("basic") }
btnPro.setOnClickListener { activatePlan("pro") }
btnPro.setOnClickListener { startPaymentFlow("pro", 10.0) }
btnAnnual.setOnClickListener { startPaymentFlow("annual", 80.0) }
btnCancel.setOnClickListener {
stopPaymentFlow()
}
}
private fun updateStatusUI() {
@@ -40,39 +59,99 @@ class SubscriptionActivity : AppCompatActivity() {
val today = SubscriptionManager.ridesToday
val planText = when(plan) {
"basic" -> "أساسي ($limit طلب / يوم)"
"pro" -> "احترافي (لا محدود)"
"annual" -> "سنوي (لا محدود)"
else -> "مجاني (1 طلب / يوم)"
}
tvStatus.text = "الخطة الحالية: $planText\nاستهلاك اليوم: $today"
tvStatus.text = "الخطة الحالية: $planText\nاستهلاك اليوم: $today / $limit"
}
private fun activatePlan(plan: String) {
// In a real app, integrate payment gateway here.
// For demonstration, we just call the API directly.
private fun startPaymentFlow(plan: String, amount: Double) {
val fingerprint = DeviceUtils.getDeviceFingerprint(this)
lifecycleScope.launch {
try {
val response = withContext(Dispatchers.IO) {
ApiClient.service.activateSubscription(
ActivateSubscriptionRequest(fingerprint, plan, "DEMO_REF_123")
)
ApiClient.service.initPayment(InitPaymentRequest(fingerprint, plan, amount))
}
if (response.success) {
Toast.makeText(this@SubscriptionActivity, "تم تفعيل الاشتراك بنجاح!", Toast.LENGTH_SHORT).show()
// Re-check subscription to update local cache
SubscriptionManager.checkSubscription(this@SubscriptionActivity)
updateStatusUI()
if (response.success && response.reference_code != null) {
showPaymentOverlay(response.reference_code, amount)
startPolling(response.reference_code)
} else {
Toast.makeText(this@SubscriptionActivity, "فشل تفعيل الاشتراك: ${response.message}", Toast.LENGTH_SHORT).show()
Toast.makeText(this@SubscriptionActivity, "فشل إنشاء طلب الدفع", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this@SubscriptionActivity, "حدث خطأ في الاتصال", Toast.LENGTH_SHORT).show()
Toast.makeText(this@SubscriptionActivity, "حدث خطأ في الاتصال بالسيرفر", Toast.LENGTH_SHORT).show()
}
}
}
private fun showPaymentOverlay(refCode: String, amount: Double) {
tvRefCode.text = refCode
layoutOverlay.visibility = View.VISIBLE
// Start 10 minute timer
var secondsLeft = 600
timerJob?.cancel()
timerJob = lifecycleScope.launch {
while (secondsLeft > 0) {
val mins = secondsLeft / 60
val secs = secondsLeft % 60
tvTimer.text = "ننتظر وصول الدفعة... (${String.format("%02d:%02d", mins, secs)})"
delay(1000)
secondsLeft--
}
stopPaymentFlow()
Toast.makeText(this@SubscriptionActivity, "انتهى وقت طلب الدفع", Toast.LENGTH_LONG).show()
}
}
private fun startPolling(refCode: String) {
val fingerprint = DeviceUtils.getDeviceFingerprint(this)
pollingJob?.cancel()
pollingJob = lifecycleScope.launch {
while (isActive) {
delay(5000) // Poll every 5 seconds
try {
val response = withContext(Dispatchers.IO) {
ApiClient.service.checkPayment(CheckPaymentRequest(refCode, fingerprint))
}
if (response.status == "paid") {
onPaymentSuccess()
break
} else if (response.status == "expired") {
stopPaymentFlow()
Toast.makeText(this@SubscriptionActivity, "انتهت صلاحية الطلب", Toast.LENGTH_SHORT).show()
break
}
} catch (e: Exception) {
// Ignore connection errors during polling
}
}
}
}
private fun onPaymentSuccess() {
stopPaymentFlow()
Toast.makeText(this, "✅ تم تفعيل الاشتراك بنجاح!", Toast.LENGTH_LONG).show()
lifecycleScope.launch {
SubscriptionManager.checkSubscription(this@SubscriptionActivity)
updateStatusUI()
}
}
private fun stopPaymentFlow() {
pollingJob?.cancel()
timerJob?.cancel()
layoutOverlay.visibility = View.GONE
}
override fun onDestroy() {
super.onDestroy()
pollingJob?.cancel()
timerJob?.cancel()
}
}

View File

@@ -67,11 +67,24 @@ data class ActivateSubscriptionRequest(
)
@Keep
data class ActivateSubscriptionResponse(
data class InitPaymentRequest(val fingerprint: String, val plan: String, val amount: Double)
@Keep
data class InitPaymentResponse(
val success: Boolean,
val message: String?,
val plan: String?,
val expires_at: String?
val reference_code: String?,
val amount: Double?,
val cliq_alias: String?,
val expires_in_minutes: Int?
)
@Keep
data class CheckPaymentRequest(val reference_code: String, val fingerprint: String)
@Keep
data class CheckPaymentResponse(
val success: Boolean,
val status: String // pending, paid, expired
)
interface BackendApiService {
@@ -86,6 +99,12 @@ interface BackendApiService {
@POST("api/subscription/activate.php")
suspend fun activateSubscription(@Body request: ActivateSubscriptionRequest): ActivateSubscriptionResponse
@POST("api/subscription/init_payment.php")
suspend fun initPayment(@Body request: InitPaymentRequest): InitPaymentResponse
@POST("api/subscription/check_payment.php")
suspend fun checkPayment(@Body request: CheckPaymentRequest): CheckPaymentResponse
}
object ApiClient {

View File

@@ -1,163 +1,236 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1A1A2E"
android:fillViewport="true">
android:background="#121222">
<LinearLayout
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="💎 باقات التميّز"
android:textColor="#00D4AA"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="سرّع عملك وضاعف دخلك اليوم"
android:textColor="#AAAAAA"
android:textSize="14sp" />
<!-- Current Status -->
<TextView
android:id="@+id/tv_current_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:background="@drawable/bg_card"
android:padding="12dp"
android:text="الحالة: جاري جلب البيانات..."
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="14sp" />
<!-- Pro Plan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_card"
android:elevation="4dp"
android:orientation="vertical"
android:padding="20dp"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="الخطة الاحترافية"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10.00 JOD / شهر"
android:textColor="#00D4AA"
android:textSize="20sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="12dp"
android:background="#33FFFFFF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✅ طلبات غير محدودة (Unlimited)\n✅ أولوية قصوى في القبول\n✅ دعم جميع التطبيقات\n✅ فلاتر السعر والمسافة المتقدمة"
android:textColor="#CCCCCC"
android:textSize="13sp"
android:lineSpacingExtra="4dp" />
<Button
android:id="@+id/btn_subscribe_pro"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_button_primary"
android:text="تفعيل عبر CliQ"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<!-- Annual Plan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="20dp"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="الخطة السنوية (الأكثر توفيراً)"
android:textColor="#FFD700"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="80.00 JOD / سنة"
android:textColor="#00D4AA"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="وفر 40 دينار سنوياً مع تفعيل دائم\nشامل جميع التحديثات القادمة"
android:textColor="#AAAAAA"
android:textSize="13sp" />
<Button
android:id="@+id/btn_subscribe_annual"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_button"
android:text="تفعيل سنوي عبر CliQ"
android:textColor="#FFFFFF" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<!-- Payment Overlay (Hidden by default) -->
<LinearLayout
android:id="@+id/layout_payment_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F2121222"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp">
android:padding="32dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="💳 الاشتراكات"
android:textColor="#00D4AA"
android:textSize="32sp"
android:text="انتظار الدفع عبر CliQ"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="اختر الخطة المناسبة لعملك"
android:layout_marginTop="8dp"
android:text="يرجى إرسال المبلغ إلى الاسم المستعار التالي:"
android:textColor="#AAAAAA"
android:textSize="16sp" />
android:textAlignment="center" />
<TextView
android:id="@+id/tv_current_status"
android:layout_width="match_parent"
android:id="@+id/tv_cliq_alias"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="الخطة الحالية: مجاني (1 طلب / يوم)"
android:layout_marginTop="8dp"
android:text="JordanBot"
android:textColor="#00D4AA"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="الرقم المرجعي (هام جداً):"
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/tv_ref_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#3300D4AA"
android:padding="12dp"
android:text="JB-XXXXXX"
android:textColor="#00D4AA"
android:textSize="32sp"
android:textStyle="bold"
android:layout_marginTop="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="⚠️ يجب كتابة الرقم أعلاه في خانة الملاحظات داخل تطبيق البنك لضمان التفعيل الآلي."
android:textColor="#FF5252"
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="16sp" />
android:textSize="12sp" />
<!-- Free Plan -->
<LinearLayout
android:layout_width="match_parent"
<ProgressBar
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="32dp"
android:indeterminateTint="#00D4AA" />
<TextView
android:id="@+id/tv_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginBottom="16dp">
android:layout_marginTop="8dp"
android:text="ننتظر وصول الدفعة... (10:00)"
android:textColor="#AAAAAA" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="المجانية"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 JOD"
android:textColor="#00D4AA"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="• طلب واحد يومياً\n• تجربة الأساسيات"
android:textColor="#AAAAAA"
android:textSize="14sp" />
</LinearLayout>
<!-- Basic Plan -->
<LinearLayout
android:layout_width="match_parent"
<Button
android:id="@+id/btn_cancel_payment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="الأساسية"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1 JOD / شهر"
android:textColor="#00D4AA"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="• 10 طلبات يومياً\n• إحصائيات أساسية\n• فلاتر متقدمة"
android:textColor="#AAAAAA"
android:textSize="14sp" />
<Button
android:id="@+id/btn_subscribe_basic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:backgroundTint="#00D4AA"
android:textColor="#FFFFFF"
android:text="اشترك الآن" />
</LinearLayout>
<!-- Pro Plan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="الاحترافية"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2.5 JOD / شهر"
android:textColor="#00D4AA"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="• طلبات غير محدودة\n• أولوية القبول\n• دعم فني مخصص"
android:textColor="#AAAAAA"
android:textSize="14sp" />
<Button
android:id="@+id/btn_subscribe_pro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:backgroundTint="#FF9800"
android:textColor="#FFFFFF"
android:text="اشترك الآن" />
</LinearLayout>
android:layout_marginTop="32dp"
android:background="?attr/selectableItemBackground"
android:text="إلغاء الطلب"
android:textColor="#888888" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View File

@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../../config/db.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method Not Allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$referenceCode = $input['reference_code'] ?? null;
$fingerprint = $input['fingerprint'] ?? null;
if (!$referenceCode || !$fingerprint) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing reference_code or fingerprint']);
exit;
}
try {
$stmt = $pdo->prepare("SELECT status FROM cliq_payments WHERE reference_code = :ref AND fingerprint = :fingerprint LIMIT 1");
$stmt->execute([':ref' => $referenceCode, ':fingerprint' => $fingerprint]);
$payment = $stmt->fetch();
if ($payment) {
// If it's still pending but older than 15 minutes, mark it as expired
if ($payment['status'] === 'pending') {
$stmtDate = $pdo->prepare("UPDATE cliq_payments SET status = 'expired' WHERE reference_code = :ref AND created_at < NOW() - INTERVAL 15 MINUTE");
$stmtDate->execute([':ref' => $referenceCode]);
// Re-fetch if we just expired it
if ($stmtDate->rowCount() > 0) {
$payment['status'] = 'expired';
}
}
echo json_encode([
'success' => true,
'status' => $payment['status'] // 'pending', 'paid', or 'expired'
]);
} else {
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Payment not found']);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}

View File

@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../../config/db.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method Not Allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$fingerprint = $input['fingerprint'] ?? null;
$plan = $input['plan'] ?? null;
$amount = $input['amount'] ?? null;
if (!$fingerprint || !$plan || !$amount) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing required fields']);
exit;
}
// Expire old pending payments for this user to avoid confusion
try {
$stmt = $pdo->prepare("UPDATE cliq_payments SET status = 'expired' WHERE fingerprint = :fingerprint AND status = 'pending'");
$stmt->execute([':fingerprint' => $fingerprint]);
// Generate a unique 6-character reference code (e.g. JB-1A2B3C)
$refCode = 'JB-' . strtoupper(substr(md5(uniqid(rand(), true)), 0, 6));
// Insert new pending payment
$stmt = $pdo->prepare("INSERT INTO cliq_payments (fingerprint, reference_code, amount, plan, status) VALUES (:fingerprint, :refCode, :amount, :plan, 'pending')");
$stmt->execute([
':fingerprint' => $fingerprint,
':refCode' => $refCode,
':amount' => $amount,
':plan' => $plan
]);
echo json_encode([
'success' => true,
'reference_code' => $refCode,
'amount' => $amount,
'cliq_alias' => 'JordanBot', // Change this to the actual CliQ alias
'expires_in_minutes' => 10
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}

View File

@@ -0,0 +1,22 @@
<?php
require_once __DIR__ . '/../../config/db.php';
try {
$sql = "CREATE TABLE IF NOT EXISTS cliq_payments (
id INT AUTO_INCREMENT PRIMARY KEY,
fingerprint VARCHAR(255) NOT NULL,
reference_code VARCHAR(20) NOT NULL UNIQUE,
amount DECIMAL(10,2) NOT NULL,
plan VARCHAR(50) NOT NULL,
status ENUM('pending', 'paid', 'expired') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (reference_code),
INDEX (fingerprint)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$pdo->exec($sql);
echo "Table 'cliq_payments' created or already exists successfully.";
} catch (PDOException $e) {
echo "Error creating table: " . $e->getMessage();
}

View File

@@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../../config/db.php';
header('Content-Type: application/json');
// This webhook is called by your SMS Bot application
// Expected fields: sender (e.g., Arab Bank), message (SMS text), timestamp
$input = json_decode(file_get_contents('php://input'), true);
$message = $input['message'] ?? '';
$sender = $input['sender'] ?? '';
if (empty($message)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Empty message']);
exit;
}
// 1. Log the incoming SMS for debugging
error_log("JordanBot SMS Received: Sender: [$sender], Content: [$message]");
// 2. Extract Reference Code (Pattern: JB-XXXXXX)
// Matches JB- followed by 6 alphanumeric characters
preg_match('/JB-([A-Z0-9]{6})/', strtoupper($message), $matches);
$refCode = isset($matches[0]) ? $matches[0] : null;
// 3. Extract Amount (Pattern: finds decimal numbers)
// Note: Jordan uses 'JOD' or 'دينار'
preg_match('/([0-9]+(\.[0-9]{2})?)/', $message, $amtMatches);
$amountReceived = isset($amtMatches[0]) ? floatval($amtMatches[0]) : 0;
if (!$refCode) {
echo json_encode(['success' => false, 'message' => 'No Reference Code found in SMS']);
exit;
}
try {
// 4. Find the pending payment
$stmt = $pdo->prepare("SELECT * FROM cliq_payments WHERE reference_code = :ref AND status = 'pending' LIMIT 1");
$stmt->execute([':ref' => $refCode]);
$payment = $stmt->fetch();
if ($payment) {
// Optional: Verify amount match (allowing for minor differences or currency symbols)
if (abs($payment['amount'] - $amountReceived) > 0.05) {
error_log("JordanBot: Amount mismatch for $refCode. Expected: {$payment['amount']}, Received: $amountReceived");
// We can still proceed or mark for manual review
}
$pdo->beginTransaction();
// 5. Update payment status
$stmt = $pdo->prepare("UPDATE cliq_payments SET status = 'paid' WHERE id = :id");
$stmt->execute([':id' => $payment['id']]);
// 6. Activate/Extend Subscription
$fingerprint = $payment['fingerprint'];
$plan = $payment['plan'];
// Calculate expiration (e.g. basic=30 days, annual=365 days)
$days = ($plan === 'annual') ? 365 : 30;
$expiresAt = date('Y-m-d H:i:s', strtotime("+$days days"));
// Check if user already has a subscription to extend it
$stmtCheck = $pdo->prepare("SELECT id, expires_at FROM subscriptions WHERE fingerprint = :fingerprint AND is_active = 1 LIMIT 1");
$stmtCheck->execute([':fingerprint' => $fingerprint]);
$existing = $stmtCheck->fetch();
if ($existing) {
// Extend existing
$newExpiry = date('Y-m-d H:i:s', strtotime($existing['expires_at'] . " +$days days"));
$stmtUpdate = $pdo->prepare("UPDATE subscriptions SET expires_at = :expiry, plan = :plan WHERE id = :id");
$stmtUpdate->execute([':expiry' => $newExpiry, ':plan' => $plan, ':id' => $existing['id']]);
} else {
// Create new
$stmtInsert = $pdo->prepare("INSERT INTO subscriptions (fingerprint, plan, expires_at, is_active) VALUES (:fingerprint, :plan, :expiry, 1)");
$stmtInsert->execute([':fingerprint' => $fingerprint, ':plan' => $plan, ':expiry' => $expiresAt]);
}
$pdo->commit();
error_log("JordanBot: Subscription activated for $fingerprint via $refCode ($plan)");
echo json_encode(['success' => true, 'message' => "Subscription activated for $refCode"]);
} else {
echo json_encode(['success' => false, 'message' => 'Reference code not found or already processed']);
}
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}