Update: 2026-05-16 01:51:22
This commit is contained in:
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,163 +1,236 @@
|
|||||||
<?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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#1A1A2E"
|
android:background="#121222">
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center_horizontal"
|
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:orientation="vertical"
|
||||||
android:padding="24dp">
|
android:padding="32dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<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="16dp"
|
android:text="انتظار الدفع عبر CliQ"
|
||||||
android:text="💳 الاشتراكات"
|
android:textColor="#FFFFFF"
|
||||||
android:textColor="#00D4AA"
|
android:textSize="24sp"
|
||||||
android:textSize="32sp"
|
|
||||||
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_marginTop="8dp"
|
||||||
android:text="اختر الخطة المناسبة لعملك"
|
android:text="يرجى إرسال المبلغ إلى الاسم المستعار التالي:"
|
||||||
android:textColor="#AAAAAA"
|
android:textColor="#AAAAAA"
|
||||||
android:textSize="16sp" />
|
android:textAlignment="center" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_current_status"
|
android:id="@+id/tv_cliq_alias"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="الخطة الحالية: مجاني (1 طلب / يوم)"
|
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:textAlignment="center"
|
||||||
android:textColor="#FFFFFF"
|
android:textSize="12sp" />
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- Free Plan -->
|
<ProgressBar
|
||||||
<LinearLayout
|
android:layout_width="40dp"
|
||||||
android:layout_width="match_parent"
|
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_height="wrap_content"
|
||||||
android:background="@drawable/bg_card"
|
android:layout_marginTop="8dp"
|
||||||
android:orientation="vertical"
|
android:text="ننتظر وصول الدفعة... (10:00)"
|
||||||
android:padding="16dp"
|
android:textColor="#AAAAAA" />
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/btn_cancel_payment"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="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"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/bg_card"
|
android:layout_marginTop="32dp"
|
||||||
android:orientation="vertical"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:padding="16dp"
|
android:text="إلغاء الطلب"
|
||||||
android:layout_marginBottom="16dp">
|
android:textColor="#888888" />
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
50
backend/api/subscription/check_payment.php
Normal file
50
backend/api/subscription/check_payment.php
Normal 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()]);
|
||||||
|
}
|
||||||
50
backend/api/subscription/init_payment.php
Normal file
50
backend/api/subscription/init_payment.php
Normal 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()]);
|
||||||
|
}
|
||||||
22
backend/api/subscription/setup_cliq_db.php
Normal file
22
backend/api/subscription/setup_cliq_db.php
Normal 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();
|
||||||
|
}
|
||||||
91
backend/api/subscription/webhook_sms.php
Normal file
91
backend/api/subscription/webhook_sms.php
Normal 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()]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user