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 package com.jordanbot.autoride
import android.os.Bundle 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.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.jordanbot.autoride.api.ActivateSubscriptionRequest
import com.jordanbot.autoride.api.ApiClient 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.subscription.SubscriptionManager
import com.jordanbot.autoride.utils.DeviceUtils import com.jordanbot.autoride.utils.DeviceUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SubscriptionActivity : AppCompatActivity() { class SubscriptionActivity : AppCompatActivity() {
private lateinit var tvStatus: TextView private lateinit var tvStatus: TextView
private lateinit var btnBasic: Button
private lateinit var btnPro: 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_subscription) setContentView(R.layout.activity_subscription)
tvStatus = findViewById(R.id.tv_current_status) tvStatus = findViewById(R.id.tv_current_status)
btnBasic = findViewById(R.id.btn_subscribe_basic)
btnPro = findViewById(R.id.btn_subscribe_pro) 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() updateStatusUI()
btnBasic.setOnClickListener { activatePlan("basic") } btnPro.setOnClickListener { startPaymentFlow("pro", 10.0) }
btnPro.setOnClickListener { activatePlan("pro") } btnAnnual.setOnClickListener { startPaymentFlow("annual", 80.0) }
btnCancel.setOnClickListener {
stopPaymentFlow()
}
} }
private fun updateStatusUI() { private fun updateStatusUI() {
@@ -40,39 +59,99 @@ class SubscriptionActivity : AppCompatActivity() {
val today = SubscriptionManager.ridesToday val today = SubscriptionManager.ridesToday
val planText = when(plan) { val planText = when(plan) {
"basic" -> "أساسي ($limit طلب / يوم)"
"pro" -> "احترافي (لا محدود)" "pro" -> "احترافي (لا محدود)"
"annual" -> "سنوي (لا محدود)" "annual" -> "سنوي (لا محدود)"
else -> "مجاني (1 طلب / يوم)" else -> "مجاني (1 طلب / يوم)"
} }
tvStatus.text = "الخطة الحالية: $planText\nاستهلاك اليوم: $today" tvStatus.text = "الخطة الحالية: $planText\nاستهلاك اليوم: $today / $limit"
} }
private fun activatePlan(plan: String) { private fun startPaymentFlow(plan: String, amount: Double) {
// In a real app, integrate payment gateway here.
// For demonstration, we just call the API directly.
val fingerprint = DeviceUtils.getDeviceFingerprint(this) val fingerprint = DeviceUtils.getDeviceFingerprint(this)
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val response = withContext(Dispatchers.IO) { val response = withContext(Dispatchers.IO) {
ApiClient.service.activateSubscription( ApiClient.service.initPayment(InitPaymentRequest(fingerprint, plan, amount))
ActivateSubscriptionRequest(fingerprint, plan, "DEMO_REF_123")
)
} }
if (response.success) { if (response.success && response.reference_code != null) {
Toast.makeText(this@SubscriptionActivity, "تم تفعيل الاشتراك بنجاح!", Toast.LENGTH_SHORT).show() showPaymentOverlay(response.reference_code, amount)
// Re-check subscription to update local cache startPolling(response.reference_code)
SubscriptionManager.checkSubscription(this@SubscriptionActivity)
updateStatusUI()
} else { } else {
Toast.makeText(this@SubscriptionActivity, "فشل تفعيل الاشتراك: ${response.message}", Toast.LENGTH_SHORT).show() Toast.makeText(this@SubscriptionActivity, "فشل إنشاء طلب الدفع", Toast.LENGTH_SHORT).show()
} }
} catch (e: Exception) { } 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 @Keep
data class ActivateSubscriptionResponse( data class InitPaymentRequest(val fingerprint: String, val plan: String, val amount: Double)
@Keep
data class InitPaymentResponse(
val success: Boolean, val success: Boolean,
val message: String?, val reference_code: String?,
val plan: String?, val amount: Double?,
val expires_at: String? 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 { interface BackendApiService {
@@ -86,6 +99,12 @@ interface BackendApiService {
@POST("api/subscription/activate.php") @POST("api/subscription/activate.php")
suspend fun activateSubscription(@Body request: ActivateSubscriptionRequest): ActivateSubscriptionResponse 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 { object ApiClient {

View File

@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?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="#121222">
<ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#1A1A2E"
android:fillViewport="true"> android:fillViewport="true">
<LinearLayout <LinearLayout
@@ -16,148 +20,217 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="💳 الاشتراكات" android:text="💎 باقات التميّز"
android:textColor="#00D4AA" android:textColor="#00D4AA"
android:textSize="32sp" android:textSize="28sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:text="اختر الخطة المناسبة لعملك" android:text="سرّع عملك وضاعف دخلك اليوم"
android:textColor="#AAAAAA" android:textColor="#AAAAAA"
android:textSize="16sp" /> android:textSize="14sp" />
<!-- Current Status -->
<TextView <TextView
android:id="@+id/tv_current_status" android:id="@+id/tv_current_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="24dp"
android:text="الخطة الحالية: مجاني (1 طلب / يوم)" android:background="@drawable/bg_card"
android:padding="12dp"
android:text="الحالة: جاري جلب البيانات..."
android:textAlignment="center" android:textAlignment="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="16sp" />
<!-- Free 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="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" /> android:textSize="14sp" />
</LinearLayout>
<!-- Basic 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="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 --> <!-- Pro Plan -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_card" android:background="@drawable/bg_card"
android:elevation="4dp"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp" android:padding="20dp"
android:layout_marginBottom="16dp"> android:layout_marginBottom="16dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="الاحترافية" android:text="الخطة الاحترافية"
android:textColor="#FFFFFF" 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:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="2.5 JOD / شهر" android:layout_marginTop="12dp"
android:textColor="#00D4AA" android:text="وفر 40 دينار سنوياً مع تفعيل دائم\nشامل جميع التحديثات القادمة"
android:textSize="18sp" 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="32dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="انتظار الدفع عبر CliQ"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="• طلبات غير محدودة\n• أولوية القبول\n• دعم فني مخصص" android:text="يرجى إرسال المبلغ إلى الاسم المستعار التالي:"
android:textColor="#AAAAAA" android:textColor="#AAAAAA"
android:textSize="14sp" /> android:textAlignment="center" />
<TextView
android:id="@+id/tv_cliq_alias"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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:textSize="12sp" />
<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:layout_marginTop="8dp"
android:text="ننتظر وصول الدفعة... (10:00)"
android:textColor="#AAAAAA" />
<Button <Button
android:id="@+id/btn_subscribe_pro" android:id="@+id/btn_cancel_payment"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="32dp"
android:backgroundTint="#FF9800" android:background="?attr/selectableItemBackground"
android:textColor="#FFFFFF" android:text="إلغاء الطلب"
android:text="اشترك الآن" /> android:textColor="#888888" />
</LinearLayout>
</LinearLayout> </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()]);
}