Update: 2026-07-13 04:13:50
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
*.log
|
||||
error_log
|
||||
@@ -0,0 +1,970 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.2.2
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Host: 127.0.0.1:3306
|
||||
-- Generation Time: Jun 16, 2026 at 05:29 PM
|
||||
-- Server version: 8.0.43-0ubuntu0.22.04.2
|
||||
-- PHP Version: 8.1.33
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Database: `WalletIntaleqDB`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `adminUser`
|
||||
--
|
||||
|
||||
CREATE TABLE `adminUser` (
|
||||
`id` int NOT NULL,
|
||||
`device_number` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`name` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `cliq_invoices`
|
||||
--
|
||||
|
||||
CREATE TABLE `cliq_invoices` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`user_id` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`user_type` varchar(20) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`cliq_phone` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_general_ci DEFAULT 'pending',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `driver`
|
||||
--
|
||||
|
||||
CREATE TABLE `driver` (
|
||||
`id` varchar(100) NOT NULL,
|
||||
`phone` varchar(20) NOT NULL,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`gender` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'Male',
|
||||
`license_type` varchar(255) DEFAULT NULL,
|
||||
`national_number` varchar(255) DEFAULT NULL,
|
||||
`name_arabic` varchar(255) DEFAULT NULL,
|
||||
`name_english` varchar(255) DEFAULT NULL,
|
||||
`issue_date` date DEFAULT NULL,
|
||||
`expiry_date` date DEFAULT NULL,
|
||||
`license_categories` varchar(255) DEFAULT NULL,
|
||||
`address` text,
|
||||
`card_id` varchar(255) DEFAULT NULL,
|
||||
`occupation` varchar(255) DEFAULT NULL,
|
||||
`licenseIssueDate` varchar(50) DEFAULT NULL,
|
||||
`religion` varchar(255) DEFAULT NULL,
|
||||
`status` varchar(20) NOT NULL DEFAULT 'notDeleted',
|
||||
`birthdate` varchar(20) NOT NULL,
|
||||
`site` varchar(255) NOT NULL,
|
||||
`first_name` varchar(255) NOT NULL,
|
||||
`last_name` varchar(255) NOT NULL,
|
||||
`accountBank` varchar(55) NOT NULL DEFAULT 'yet',
|
||||
`bankCode` varchar(20) NOT NULL DEFAULT 'CIB',
|
||||
`education` varchar(255) DEFAULT NULL,
|
||||
`employmentType` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||
`maritalStatus` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||
`fullNameMaritial` varchar(255) DEFAULT NULL,
|
||||
`expirationDate` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `driverToken`
|
||||
--
|
||||
|
||||
CREATE TABLE `driverToken` (
|
||||
`id` int NOT NULL,
|
||||
`token` varchar(255) NOT NULL,
|
||||
`captain_id` varchar(255) NOT NULL,
|
||||
`fingerPrint` varchar(100) NOT NULL,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `driverWallet`
|
||||
--
|
||||
|
||||
CREATE TABLE `driverWallet` (
|
||||
`id` int NOT NULL,
|
||||
`driverID` varchar(100) NOT NULL,
|
||||
`paymentID` varchar(200) NOT NULL,
|
||||
`dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`amount` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
|
||||
`paymentMethod` varchar(20) NOT NULL,
|
||||
`dateUpdated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `driver_withdrawal_requests`
|
||||
--
|
||||
|
||||
CREATE TABLE `driver_withdrawal_requests` (
|
||||
`id` int NOT NULL,
|
||||
`driver_id` varchar(255) NOT NULL,
|
||||
`driver_name` varchar(255) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`wallet_type` varchar(50) NOT NULL,
|
||||
`wallet_number` varchar(50) NOT NULL,
|
||||
`status` varchar(50) NOT NULL DEFAULT 'pending',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ecash_transactions`
|
||||
--
|
||||
|
||||
CREATE TABLE `ecash_transactions` (
|
||||
`id` bigint UNSIGNED NOT NULL,
|
||||
`order_ref` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`passenger_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ecash_transaction_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ecash_transactions_driver`
|
||||
--
|
||||
|
||||
CREATE TABLE `ecash_transactions_driver` (
|
||||
`id` bigint UNSIGNED NOT NULL,
|
||||
`order_ref` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`driver_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ecash_transaction_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `invoices_shamcash`
|
||||
--
|
||||
|
||||
CREATE TABLE `invoices_shamcash` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` int NOT NULL,
|
||||
`driverID` varchar(44) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` enum('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
|
||||
`transaction_id` varchar(50) DEFAULT NULL COMMENT 'رقم عملية شام كاش لمنع التكرار',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`paid_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `invoices_shamcash_passenger`
|
||||
--
|
||||
|
||||
CREATE TABLE `invoices_shamcash_passenger` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` int NOT NULL,
|
||||
`passengerID` varchar(33) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` enum('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
|
||||
`transaction_id` varchar(50) DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`paid_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `invoices_sms`
|
||||
--
|
||||
|
||||
CREATE TABLE `invoices_sms` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` varchar(20) NOT NULL,
|
||||
`user_phone` varchar(20) NOT NULL,
|
||||
`amount` decimal(10,3) NOT NULL,
|
||||
`status` enum('pending','completed','failed') NOT NULL DEFAULT 'pending',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`driverID` varchar(33) DEFAULT NULL,
|
||||
`currency` varchar(8) DEFAULT 'SYP'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `invoices_sms_passenger`
|
||||
--
|
||||
|
||||
CREATE TABLE `invoices_sms_passenger` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` varchar(20) NOT NULL,
|
||||
`user_phone` varchar(20) NOT NULL,
|
||||
`amount` decimal(10,0) NOT NULL,
|
||||
`status` enum('pending','completed','failed') NOT NULL DEFAULT 'pending',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`passengerID` varchar(33) NOT NULL,
|
||||
`currency` varchar(8) NOT NULL DEFAULT 'SYP'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `kazan`
|
||||
--
|
||||
|
||||
CREATE TABLE `kazan` (
|
||||
`id` int NOT NULL,
|
||||
`country` varchar(30) NOT NULL,
|
||||
`kazan` varchar(10) NOT NULL,
|
||||
`comfortPrice` varchar(10) NOT NULL,
|
||||
`speedPrice` varchar(10) NOT NULL,
|
||||
`deliveryPrice` varchar(11) NOT NULL,
|
||||
`freePrice` varchar(11) NOT NULL,
|
||||
`latePrice` varchar(10) NOT NULL,
|
||||
`heavyPrice` varchar(10) NOT NULL,
|
||||
`adminId` varchar(100) NOT NULL,
|
||||
`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`naturePrice` varchar(10) NOT NULL,
|
||||
`fuelPrice` varchar(6) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `mtn_invoices`
|
||||
--
|
||||
|
||||
CREATE TABLE `mtn_invoices` (
|
||||
`id` int NOT NULL,
|
||||
`invoice_number` varchar(20) NOT NULL,
|
||||
`user_id` varchar(50) NOT NULL,
|
||||
`user_type` enum('driver','passenger') NOT NULL COMMENT 'يحدد هل المستخدم سائق أم راكب',
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`mtn_phone` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'رقم MTN الذي سيتم الدفع منه',
|
||||
`status` enum('pending','completed','failed','expired') NOT NULL DEFAULT 'pending',
|
||||
`mtn_transaction_id` varchar(255) DEFAULT NULL COMMENT 'معرّف العملية من طرف MTN بعد الدفع',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `passengers`
|
||||
--
|
||||
|
||||
CREATE TABLE `passengers` (
|
||||
`id` varchar(100) NOT NULL,
|
||||
`phone` varchar(15) NOT NULL,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`password` varchar(100) NOT NULL,
|
||||
`gender` varchar(25) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
|
||||
`status` varchar(20) NOT NULL DEFAULT 'notDeleted',
|
||||
`birthdate` varchar(44) NOT NULL,
|
||||
`site` varchar(255) NOT NULL,
|
||||
`first_name` varchar(255) NOT NULL,
|
||||
`last_name` varchar(255) NOT NULL,
|
||||
`sosPhone` varchar(33) NOT NULL DEFAULT 'sos',
|
||||
`education` varchar(30) NOT NULL DEFAULT 'none',
|
||||
`employmentType` varchar(30) NOT NULL DEFAULT 'none',
|
||||
`maritalStatus` varchar(30) NOT NULL DEFAULT 'none',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `passengerWallet`
|
||||
--
|
||||
|
||||
CREATE TABLE `passengerWallet` (
|
||||
`id` int NOT NULL,
|
||||
`passenger_id` varchar(100) NOT NULL,
|
||||
`balance` decimal(10,2) NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payments`
|
||||
--
|
||||
|
||||
CREATE TABLE `payments` (
|
||||
`id` varchar(111) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`payment_method` varchar(255) NOT NULL,
|
||||
`passengerID` varchar(100) NOT NULL,
|
||||
`rideId` varchar(100) NOT NULL,
|
||||
`driverID` varchar(100) NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`isGiven` varchar(20) NOT NULL DEFAULT 'waiting'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `paymentsDriverPoints`
|
||||
--
|
||||
|
||||
CREATE TABLE `paymentsDriverPoints` (
|
||||
`id` int NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`payment_method` varchar(50) NOT NULL,
|
||||
`driverID` varchar(60) NOT NULL,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `paymentsLog`
|
||||
--
|
||||
|
||||
CREATE TABLE `paymentsLog` (
|
||||
`id` int NOT NULL,
|
||||
`payment_id` varchar(255) NOT NULL,
|
||||
`user_id` char(40) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` tinyint NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `paymentsLogSyria`
|
||||
--
|
||||
|
||||
CREATE TABLE `paymentsLogSyria` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` tinyint NOT NULL DEFAULT '0',
|
||||
`order_ref` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`payment_method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'ecash',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `paymentsLogSyriaDriver`
|
||||
--
|
||||
|
||||
CREATE TABLE `paymentsLogSyriaDriver` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||
`status` tinyint NOT NULL DEFAULT '2',
|
||||
`processed_wallet` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`order_ref` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`payment_method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'ecash',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payment_log_driver`
|
||||
--
|
||||
|
||||
CREATE TABLE `payment_log_driver` (
|
||||
`id` int NOT NULL,
|
||||
`payment_id` varchar(255) NOT NULL,
|
||||
`user_id` char(70) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payment_tokens`
|
||||
--
|
||||
|
||||
CREATE TABLE `payment_tokens` (
|
||||
`id` int NOT NULL,
|
||||
`token` varchar(255) NOT NULL,
|
||||
`driverID` varchar(255) NOT NULL,
|
||||
`dateCreated` datetime NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`isUsed` tinyint(1) DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payment_tokens_passenger`
|
||||
--
|
||||
|
||||
CREATE TABLE `payment_tokens_passenger` (
|
||||
`id` int NOT NULL,
|
||||
`token` varchar(255) NOT NULL,
|
||||
`passengerId` varchar(255) NOT NULL,
|
||||
`dateCreated` datetime NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`isUsed` tinyint(1) DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payout_requests`
|
||||
--
|
||||
|
||||
CREATE TABLE `payout_requests` (
|
||||
`id` int NOT NULL,
|
||||
`driver_id` varchar(35) NOT NULL,
|
||||
`driver_phone` varchar(20) NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`wallet_type` varchar(50) DEFAULT NULL,
|
||||
`status` enum('pending','completed','failed') NOT NULL DEFAULT 'pending',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`completed_at` timestamp NULL DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `phone_verification`
|
||||
--
|
||||
|
||||
CREATE TABLE `phone_verification` (
|
||||
`id` int NOT NULL,
|
||||
`phone_number` varchar(20) NOT NULL,
|
||||
`driverId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'yet',
|
||||
`email` varchar(50) NOT NULL DEFAULT 'yet',
|
||||
`token_code` varchar(10) NOT NULL,
|
||||
`expiration_time` datetime NOT NULL,
|
||||
`is_verified` tinyint(1) DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `phone_verification_passenger`
|
||||
--
|
||||
|
||||
CREATE TABLE `phone_verification_passenger` (
|
||||
`id` int NOT NULL,
|
||||
`phone_number` varchar(15) NOT NULL,
|
||||
`token` varchar(6) NOT NULL,
|
||||
`expiration_time` datetime NOT NULL,
|
||||
`verified` tinyint(1) DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `raw_sms_log`
|
||||
--
|
||||
|
||||
CREATE TABLE `raw_sms_log` (
|
||||
`id` int NOT NULL,
|
||||
`sender` varchar(50) NOT NULL,
|
||||
`message_body` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`status` enum('pending','processed','failed') NOT NULL DEFAULT 'pending',
|
||||
`gemini_result` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `siroWallet`
|
||||
--
|
||||
|
||||
CREATE TABLE `siroWallet` (
|
||||
`id` int NOT NULL,
|
||||
`driverId` varchar(100) NOT NULL,
|
||||
`passengerId` varchar(100) NOT NULL,
|
||||
`amount` varchar(10) NOT NULL,
|
||||
`paymentMethod` varchar(50) NOT NULL,
|
||||
`token` varchar(100) NOT NULL,
|
||||
`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `smsSender`
|
||||
--
|
||||
|
||||
CREATE TABLE `smsSender` (
|
||||
`id` int NOT NULL,
|
||||
`senderId` varchar(20) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tokens`
|
||||
--
|
||||
|
||||
CREATE TABLE `tokens` (
|
||||
`id` int NOT NULL,
|
||||
`token` varchar(333) NOT NULL,
|
||||
`passengerID` varchar(111) NOT NULL,
|
||||
`fingerPrint` varchar(300) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `adminUser`
|
||||
--
|
||||
ALTER TABLE `adminUser`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `cliq_invoices`
|
||||
--
|
||||
ALTER TABLE `cliq_invoices`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `invoice_number` (`invoice_number`);
|
||||
|
||||
--
|
||||
-- Indexes for table `driver`
|
||||
--
|
||||
ALTER TABLE `driver`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `phone` (`phone`,`email`),
|
||||
ADD UNIQUE KEY `national_number` (`national_number`);
|
||||
|
||||
--
|
||||
-- Indexes for table `driverToken`
|
||||
--
|
||||
ALTER TABLE `driverToken`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `driverWallet`
|
||||
--
|
||||
ALTER TABLE `driverWallet`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `driver_withdrawal_requests`
|
||||
--
|
||||
ALTER TABLE `driver_withdrawal_requests`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `ecash_transactions`
|
||||
--
|
||||
ALTER TABLE `ecash_transactions`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `order_ref_unique` (`order_ref`),
|
||||
ADD KEY `user_id_index` (`user_id`),
|
||||
ADD KEY `passenger_id_index` (`passenger_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `ecash_transactions_driver`
|
||||
--
|
||||
ALTER TABLE `ecash_transactions_driver`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `order_ref_unique_driver` (`order_ref`),
|
||||
ADD KEY `driver_id_index` (`driver_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `invoices_shamcash`
|
||||
--
|
||||
ALTER TABLE `invoices_shamcash`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `invoice_number` (`invoice_number`),
|
||||
ADD KEY `status` (`status`);
|
||||
|
||||
--
|
||||
-- Indexes for table `invoices_shamcash_passenger`
|
||||
--
|
||||
ALTER TABLE `invoices_shamcash_passenger`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `invoice_number` (`invoice_number`),
|
||||
ADD KEY `status` (`status`);
|
||||
|
||||
--
|
||||
-- Indexes for table `invoices_sms`
|
||||
--
|
||||
ALTER TABLE `invoices_sms`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `invoice_number` (`invoice_number`),
|
||||
ADD KEY `idx_user_phone` (`user_phone`),
|
||||
ADD KEY `idx_status` (`status`);
|
||||
|
||||
--
|
||||
-- Indexes for table `invoices_sms_passenger`
|
||||
--
|
||||
ALTER TABLE `invoices_sms_passenger`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `uniq_invoice_number_passenger` (`invoice_number`);
|
||||
|
||||
--
|
||||
-- Indexes for table `kazan`
|
||||
--
|
||||
ALTER TABLE `kazan`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `mtn_invoices`
|
||||
--
|
||||
ALTER TABLE `mtn_invoices`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `invoice_number` (`invoice_number`),
|
||||
ADD KEY `idx_user` (`user_id`,`user_type`),
|
||||
ADD KEY `idx_status` (`status`);
|
||||
|
||||
--
|
||||
-- Indexes for table `passengers`
|
||||
--
|
||||
ALTER TABLE `passengers`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `phone` (`phone`,`email`);
|
||||
|
||||
--
|
||||
-- Indexes for table `passengerWallet`
|
||||
--
|
||||
ALTER TABLE `passengerWallet`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `passenger_id` (`passenger_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payments`
|
||||
--
|
||||
ALTER TABLE `payments`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `rideId` (`rideId`);
|
||||
|
||||
--
|
||||
-- Indexes for table `paymentsDriverPoints`
|
||||
--
|
||||
ALTER TABLE `paymentsDriverPoints`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `paymentsLog`
|
||||
--
|
||||
ALTER TABLE `paymentsLog`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `payment_id` (`payment_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `paymentsLogSyria`
|
||||
--
|
||||
ALTER TABLE `paymentsLogSyria`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `paymentsLogSyriaDriver`
|
||||
--
|
||||
ALTER TABLE `paymentsLogSyriaDriver`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `order_ref_index` (`order_ref`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payment_log_driver`
|
||||
--
|
||||
ALTER TABLE `payment_log_driver`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `payment_id` (`payment_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payment_tokens`
|
||||
--
|
||||
ALTER TABLE `payment_tokens`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payment_tokens_passenger`
|
||||
--
|
||||
ALTER TABLE `payment_tokens_passenger`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payout_requests`
|
||||
--
|
||||
ALTER TABLE `payout_requests`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `driver_id_status` (`driver_id`,`status`);
|
||||
|
||||
--
|
||||
-- Indexes for table `phone_verification`
|
||||
--
|
||||
ALTER TABLE `phone_verification`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `phone_verification_passenger`
|
||||
--
|
||||
ALTER TABLE `phone_verification_passenger`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `phone_number` (`phone_number`);
|
||||
|
||||
--
|
||||
-- Indexes for table `raw_sms_log`
|
||||
--
|
||||
ALTER TABLE `raw_sms_log`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `siroWallet`
|
||||
--
|
||||
ALTER TABLE `siroWallet`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `smsSender`
|
||||
--
|
||||
ALTER TABLE `smsSender`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `tokens`
|
||||
--
|
||||
ALTER TABLE `tokens`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `passengerID` (`passengerID`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `adminUser`
|
||||
--
|
||||
ALTER TABLE `adminUser`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `cliq_invoices`
|
||||
--
|
||||
ALTER TABLE `cliq_invoices`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `driverToken`
|
||||
--
|
||||
ALTER TABLE `driverToken`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `driverWallet`
|
||||
--
|
||||
ALTER TABLE `driverWallet`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `driver_withdrawal_requests`
|
||||
--
|
||||
ALTER TABLE `driver_withdrawal_requests`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `ecash_transactions`
|
||||
--
|
||||
ALTER TABLE `ecash_transactions`
|
||||
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `ecash_transactions_driver`
|
||||
--
|
||||
ALTER TABLE `ecash_transactions_driver`
|
||||
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `invoices_shamcash`
|
||||
--
|
||||
ALTER TABLE `invoices_shamcash`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `invoices_shamcash_passenger`
|
||||
--
|
||||
ALTER TABLE `invoices_shamcash_passenger`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `invoices_sms`
|
||||
--
|
||||
ALTER TABLE `invoices_sms`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `invoices_sms_passenger`
|
||||
--
|
||||
ALTER TABLE `invoices_sms_passenger`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `kazan`
|
||||
--
|
||||
ALTER TABLE `kazan`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `mtn_invoices`
|
||||
--
|
||||
ALTER TABLE `mtn_invoices`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `passengerWallet`
|
||||
--
|
||||
ALTER TABLE `passengerWallet`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `paymentsDriverPoints`
|
||||
--
|
||||
ALTER TABLE `paymentsDriverPoints`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `paymentsLog`
|
||||
--
|
||||
ALTER TABLE `paymentsLog`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `paymentsLogSyria`
|
||||
--
|
||||
ALTER TABLE `paymentsLogSyria`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `paymentsLogSyriaDriver`
|
||||
--
|
||||
ALTER TABLE `paymentsLogSyriaDriver`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `payment_log_driver`
|
||||
--
|
||||
ALTER TABLE `payment_log_driver`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `payment_tokens`
|
||||
--
|
||||
ALTER TABLE `payment_tokens`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `payment_tokens_passenger`
|
||||
--
|
||||
ALTER TABLE `payment_tokens_passenger`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `payout_requests`
|
||||
--
|
||||
ALTER TABLE `payout_requests`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `phone_verification`
|
||||
--
|
||||
ALTER TABLE `phone_verification`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `phone_verification_passenger`
|
||||
--
|
||||
ALTER TABLE `phone_verification_passenger`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `raw_sms_log`
|
||||
--
|
||||
ALTER TABLE `raw_sms_log`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `siroWallet`
|
||||
--
|
||||
ALTER TABLE `siroWallet`
|
||||
ADD AUTO_INCREMENT = 1;
|
||||
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `smsSender`
|
||||
--
|
||||
ALTER TABLE `smsSender`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `tokens`
|
||||
--
|
||||
ALTER TABLE `tokens`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
--
|
||||
-- Table structure for table `admin_audit_log`
|
||||
--
|
||||
|
||||
CREATE TABLE `admin_audit_log` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`admin_id` varchar(150) NOT NULL,
|
||||
`action` varchar(255) NOT NULL,
|
||||
`table_name` varchar(255) DEFAULT NULL,
|
||||
`record_id` varchar(255) DEFAULT NULL,
|
||||
`details` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
Executable
+292
@@ -0,0 +1,292 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>الدليل التفاعلي للتكامل بين Intaleq و MTN</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Tajawal', sans-serif; scroll-behavior: smooth; }
|
||||
.code-block { background-color: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 0.5rem; direction: ltr; text-align: left; font-family: 'Courier New', Courier, monospace; }
|
||||
.tab-active { border-color: #3b82f6; color: #3b82f6; background-color: #eff6ff; }
|
||||
.tab-inactive { border-color: transparent; color: #4b5563; }
|
||||
.endpoint-section { display: none; }
|
||||
.endpoint-section.active { display: block; }
|
||||
.flow-step { position: relative; padding-right: 40px; }
|
||||
.flow-step:not(:last-child)::before { content: ''; position: absolute; right: 15px; top: 40px; bottom: -20px; width: 2px; background-color: #d1d5db; }
|
||||
.flow-number { position: absolute; right: 0; top: 0; width: 32px; height: 32px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-50 text-slate-800">
|
||||
<div class="max-w-6xl mx-auto p-4 md:p-8">
|
||||
|
||||
<header class="text-center mb-12">
|
||||
<div class="inline-block bg-blue-600 text-white p-4 rounded-full shadow-lg mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
</div>
|
||||
<h1 class="text-4xl font-extrabold text-slate-900">الدليل التفاعلي للتكامل بين Intaleq و MTN</h1>
|
||||
<p class="mt-4 text-lg text-slate-600 max-w-3xl mx-auto">دليلك الكامل لفهم واختبار آلية الدفع عبر MTN. تم تصميم هذا الدليل لتسهيل عملية التطوير وضمان تكامل سلس وفعال.</p>
|
||||
</header>
|
||||
|
||||
<div class="bg-white p-8 rounded-2xl shadow-lg border border-slate-200">
|
||||
|
||||
<!-- Flow Section -->
|
||||
<section id="flow" class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6 border-r-4 border-blue-500 pr-4">آلية عمل دورة الدفع</h2>
|
||||
<div class="space-y-8">
|
||||
<div class="flow-step">
|
||||
<div class="flow-number flex items-center justify-center bg-slate-200 text-slate-600 rounded-full font-bold text-lg">1</div>
|
||||
<h3 class="font-bold text-lg text-slate-800">إنشاء الفاتورة</h3>
|
||||
<p class="text-slate-600">يبدأ المستخدم (سائق/راكب) عملية الدفع من تطبيق Intaleq، فيقوم نظامنا بإنشاء فاتورة داخلية بحالة "انتظار".</p>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-number flex items-center justify-center bg-slate-200 text-slate-600 rounded-full font-bold text-lg">2</div>
|
||||
<h3 class="font-bold text-lg text-slate-800">استعلام MTN</h3>
|
||||
<p class="text-slate-600">عندما يقوم المستخدم بفتح تطبيق MTN Cash Mobile للدفع، يقوم سيرفر MTN بإرسال طلب استعلام إلى سيرفرنا باستخدام رقم هاتف المستخدم للتحقق من وجود فاتورة معلقة وقيمتها.</p>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-number flex items-center justify-center bg-slate-200 text-slate-600 rounded-full font-bold text-lg">3</div>
|
||||
<h3 class="font-bold text-lg text-slate-800">تأكيد الدفع</h3>
|
||||
<p class="text-slate-600">بعد أن يكمل المستخدم عملية الدفع بنجاح في تطبيق MTN، يقوم سيرفر MTN بإرسال طلب تأكيد (Webhook) إلى سيرفرنا يحتوي على تفاصيل العملية الناجحة.</p>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-number flex items-center justify-center bg-green-500 text-white rounded-full font-bold text-lg">4</div>
|
||||
<h3 class="font-bold text-lg text-slate-800">إتمام العملية</h3>
|
||||
<p class="text-slate-600">يتحقق سيرفرنا من صحة طلب التأكيد، ويقوم بتحديث حالة الفاتورة إلى "مكتملة"، ثم يضيف الرصيد تلقائياً إلى محفظة المستخدم في تطبيق Intaleq.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Security Section -->
|
||||
<section id="security" class="mb-12 p-6 bg-slate-100 rounded-xl">
|
||||
<h2 class="text-2xl font-bold mb-4 border-r-4 border-blue-500 pr-4">آلية الحماية والتوثيق</h2>
|
||||
<p class="text-slate-700 mb-4">لضمان أن جميع الطلبات تأتي من مصدر موثوق (سيرفرات MTN حصراً)، نعتمد على آلية المفتاح السري المشترك (Shared Secret Key). يجب على سيرفراتكم إرسال هذا المفتاح في كل طلب يتم إرساله إلى نقاط النهاية الخاصة بنا.</p>
|
||||
<div class="bg-white p-4 rounded-lg shadow-sm">
|
||||
<p class="font-semibold">الهيدر المطلوب: <code class="text-red-600">X-AUTH-TOKEN</code></p>
|
||||
<div class="bg-orange-50 border border-orange-200 p-3 rounded-md mt-2">
|
||||
<p class="text-sm text-orange-800 font-medium">سيتم تزويدكم بالمفتاح السري (Secret Key) بشكل آمن عبر قنوات التواصل الرسمية.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- IP Whitelisting Section -->
|
||||
<section id="ip-whitelisting" class="mb-12 p-6 bg-yellow-50 border border-yellow-300 rounded-xl">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-yellow-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
|
||||
</div>
|
||||
<div class="mr-4">
|
||||
<h2 class="text-2xl font-bold mb-4 border-r-4 border-yellow-500 pr-4">زيادة مستوى الأمان: القائمة البيضاء (IP Whitelisting)</h2>
|
||||
<p class="text-yellow-800 mb-3">للوصول إلى أعلى مستويات الأمان وحماية التكامل، نعتمد آلية القائمة البيضاء لعناوين IP. هذه الآلية تضمن أن سيرفراتنا لن تقبل الطلبات إلا من سيرفرات MTN المصرح بها حصراً.</p>
|
||||
<div class="bg-white p-4 rounded-lg shadow-sm border border-yellow-200">
|
||||
<p class="font-bold text-slate-800">الإجراء المطلوب:</p>
|
||||
<p class="mt-2 text-slate-700">نرجو منكم تزويدنا بقائمة ثابتة وكاملة لجميع عناوين IP العامة (Public IPs) التي تستخدمونها لإرسال الطلبات إلى نقاط النهاية الخاصة بنا، ليقوم فريقنا بإضافتها إلى جدار الحماية.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- API Endpoints Section -->
|
||||
<section id="api-endpoints">
|
||||
<h2 class="text-2xl font-bold mb-6 border-r-4 border-blue-500 pr-4">نقاط النهاية (API Endpoints)</h2>
|
||||
<div class="flex border-b border-slate-200 mb-6">
|
||||
<button class="api-tab p-4 text-lg font-semibold border-b-2 tab-active" onclick="showEndpoint('query')">1. الاستعلام عن فاتورة</button>
|
||||
<button class="api-tab p-4 text-lg font-semibold border-b-2 tab-inactive" onclick="showEndpoint('webhook')">2. تأكيد الدفع (Webhook)</button>
|
||||
</div>
|
||||
|
||||
<!-- Query Invoice Endpoint -->
|
||||
<div id="query-section" class="endpoint-section active">
|
||||
<h3 class="text-xl font-bold mb-2">نقطة النهاية: الاستعلام عن فاتورة</h3>
|
||||
<p class="mb-4 text-slate-600">تستخدمها سيرفرات MTN للتحقق من وجود فاتورة معلقة لمستخدم معين قبل عرضها له في تطبيق الدفع.</p>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 class="font-semibold mb-2">تفاصيل الطلب:</h4>
|
||||
<div class="bg-slate-50 p-4 rounded-lg border border-slate-200 space-y-3">
|
||||
<p><strong>Method:</strong> <span class="bg-sky-100 text-sky-800 font-mono text-sm font-bold mr-2 px-2.5 py-0.5 rounded">GET</span></p>
|
||||
<div>
|
||||
<p><strong>URL:</strong></p>
|
||||
<div class="flex items-center">
|
||||
<code class="text-sm break-all flex-grow" id="queryUrl">https://walletintaleq.intaleq.xyz/v1/main/ride/mtn_new/query_mtn_invoice.php</code>
|
||||
<button onclick="copyToClipboard('queryUrl')" class="text-blue-500 hover:text-blue-700 text-xs mr-2 flex-shrink-0">نسخ</button>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>Header:</strong> <code class="text-sm">X-AUTH-TOKEN: [المفتاح السري]</code></p>
|
||||
<p><strong>Query Parameter:</strong></p>
|
||||
<ul class="list-disc pr-6 text-sm">
|
||||
<li><code>phone_number</code> (إلزامي): رقم هاتف المستخدم.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-2">جرّب الآن:</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="query-phone" class="block text-sm font-medium text-slate-700">رقم الهاتف:</label>
|
||||
<input type="text" id="query-phone" class="mt-1 block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500" placeholder="e.g., 9639xxxxxxxx">
|
||||
</div>
|
||||
<button onclick="testQuery()" class="w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-blue-700 transition duration-300">إرسال طلب استعلام</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<h4 class="font-semibold mb-2">الاستجابات المتوقعة:</h4>
|
||||
<pre id="query-response" class="code-block min-h-[100px]"><code>// The response from the server will appear here...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhook Handler Endpoint -->
|
||||
<div id="webhook-section" class="endpoint-section">
|
||||
<h3 class="text-xl font-bold mb-2">نقطة النهاية: تأكيد الدفع (Webhook)</h3>
|
||||
<p class="mb-4 text-slate-600">بعد إتمام الدفع، يجب على سيرفرات MTN إرسال طلب إلى نقطة النهاية هذه لتأكيد العملية وإضافة الرصيد للمستخدم.</p>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 class="font-semibold mb-2">تفاصيل الطلب:</h4>
|
||||
<div class="bg-slate-50 p-4 rounded-lg border border-slate-200 space-y-3">
|
||||
<p><strong>Method:</strong> <span class="bg-green-100 text-green-800 font-mono text-sm font-bold mr-2 px-2.5 py-0.5 rounded">POST</span></p>
|
||||
<div>
|
||||
<p><strong>URL:</strong></p>
|
||||
<div class="flex items-center">
|
||||
<code class="text-sm break-all flex-grow" id="webhookUrl">https://walletintaleq.intaleq.xyz/v1/main/ride/mtn_new/mtn_webhook_handler.php</code>
|
||||
<button onclick="copyToClipboard('webhookUrl')" class="text-blue-500 hover:text-blue-700 text-xs mr-2 flex-shrink-0">نسخ</button>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>Header:</strong> <code class="text-sm">X-AUTH-TOKEN: [المفتاح السري]</code></p>
|
||||
<p><strong>Body (JSON):</strong> انظر هيكل البيانات أدناه.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-2">جرّب الآن:</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="webhook-invoice" class="block text-sm font-medium text-slate-700">رقم الفاتورة:</label>
|
||||
<input type="text" id="webhook-invoice" class="mt-1 block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm" value="MTN-FAKE-12345">
|
||||
</div>
|
||||
<button onclick="testWebhook()" class="w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-blue-700 transition duration-300">إرسال طلب تأكيد</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<h4 class="font-semibold mb-2">هيكل JSON Body والاستجابات المتوقعة:</h4>
|
||||
<pre id="webhook-response" class="code-block min-h-[200px]"><code>// The response from the server will appear here...
|
||||
|
||||
// Example Request Body:
|
||||
{
|
||||
"invoice_number": "MTN-FAKE-12345",
|
||||
"transaction_id": "MTN_TRX_ABC123456",
|
||||
"amount_paid": 50000.00,
|
||||
"status": "success",
|
||||
"payment_timestamp": "2023-03-15T12:00:00Z"
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<footer class="text-center mt-8 text-sm text-slate-500">
|
||||
<p>© 2025 Intaleq. All rights reserved.</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showEndpoint(endpoint) {
|
||||
document.querySelectorAll('.endpoint-section').forEach(section => {
|
||||
section.classList.remove('active');
|
||||
});
|
||||
document.getElementById(endpoint + '-section').classList.add('active');
|
||||
|
||||
document.querySelectorAll('.api-tab').forEach(tab => {
|
||||
tab.classList.remove('tab-active');
|
||||
tab.classList.add('tab-inactive');
|
||||
});
|
||||
event.currentTarget.classList.add('tab-active');
|
||||
event.currentTarget.classList.remove('tab-inactive');
|
||||
}
|
||||
|
||||
function copyToClipboard(elementId) {
|
||||
const text = document.getElementById(elementId).innerText;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('تم نسخ: ' + text);
|
||||
}, (err) => {
|
||||
alert('فشل النسخ: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
function testQuery() {
|
||||
const phone = document.getElementById('query-phone').value;
|
||||
const responseEl = document.getElementById('query-response');
|
||||
|
||||
if (!phone) {
|
||||
responseEl.innerHTML = `<code>{\n "status": "error",\n "message": "الرجاء إدخال رقم هاتف."\n}</code>`;
|
||||
return;
|
||||
}
|
||||
|
||||
responseEl.innerHTML = `<code>// Sending request...</code>`;
|
||||
|
||||
// Simulate server responses based on phone number
|
||||
setTimeout(() => {
|
||||
let response = {};
|
||||
if (phone.includes('963911111111')) { // Success case
|
||||
response = {
|
||||
status: "success",
|
||||
invoice_number: "MTN-" + Date.now(),
|
||||
amount: 50000.00,
|
||||
user_name: "محمد الأحمد",
|
||||
user_type: "driver"
|
||||
};
|
||||
} else if (phone.includes('963922222222')) { // No pending invoice
|
||||
response = {
|
||||
status: "not_found",
|
||||
message: "No pending invoice found for this user."
|
||||
};
|
||||
} else { // Generic error / user not found
|
||||
response = {
|
||||
status: "error",
|
||||
message: "User not found or invalid phone number."
|
||||
};
|
||||
}
|
||||
responseEl.innerHTML = `<code>${JSON.stringify(response, null, 4)}</code>`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function testWebhook() {
|
||||
const invoice = document.getElementById('webhook-invoice').value;
|
||||
const responseEl = document.getElementById('webhook-response');
|
||||
|
||||
if (!invoice) {
|
||||
responseEl.innerHTML = `<code>{\n "status": "error",\n "message": "الرجاء إدخال رقم فاتورة."\n}</code>`;
|
||||
return;
|
||||
}
|
||||
|
||||
responseEl.innerHTML = `<code>// Sending request...</code>`;
|
||||
|
||||
setTimeout(() => {
|
||||
let response = {};
|
||||
if(invoice.includes('12345')){ // Success
|
||||
response = {
|
||||
status: "success",
|
||||
message: "Transaction finalized."
|
||||
};
|
||||
} else if (invoice.includes('67890')) { // Already processed
|
||||
response = {
|
||||
status: "error",
|
||||
message: "Invoice not found or already processed."
|
||||
};
|
||||
} else { // Generic invalid
|
||||
response = {
|
||||
status: "error",
|
||||
message: "Invalid or missing parameters."
|
||||
};
|
||||
}
|
||||
responseEl.innerHTML = `<code>${JSON.stringify(response, null, 4)}</code>`;
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
// token128.php — توليد توكن آمن 128 حرف (hex)
|
||||
|
||||
try {
|
||||
// 64 bytes -> 128 hex chars
|
||||
$token = bin2hex(random_bytes(64));
|
||||
echo "Token (128 chars): " . $token . PHP_EOL;
|
||||
} catch (Exception $e) {
|
||||
// random_bytes قد يرمي Exception إذا لم تتوفر مصادر عشوائية كافية
|
||||
fwrite(STDERR, "Failed to generate token: " . $e->getMessage() . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# =============================================================================
|
||||
# Wallet Payment Server - Environment Configuration
|
||||
# =============================================================================
|
||||
# Copy this to .env and fill in values:
|
||||
# cp .env.example .env
|
||||
# Or deploy to: /home/intaleq-walletintaleq/env/.env
|
||||
# =============================================================================
|
||||
|
||||
# Database
|
||||
dbname=WalletIntaleqDB
|
||||
USER=root
|
||||
PASS=<CHANGE_ME>
|
||||
|
||||
# JWT / Security
|
||||
SECRET_KEY=<CHANGE_ME>
|
||||
SECRET_KEY_HMAC=<CHANGE_ME>
|
||||
FP_PEPPER=<CHANGE_ME>
|
||||
S2S_SHARED_KEY=<CHANGE_ME>
|
||||
PAYMENT_KEY=<CHANGE_ME>
|
||||
WEBHOOK_AUTH_TOKEN=<CHANGE_ME>
|
||||
CRON_KEY=<CHANGE_ME>
|
||||
|
||||
# Encryption
|
||||
keyOfApp=<32_byte_hex>
|
||||
initializationVector=<16_byte_hex>
|
||||
|
||||
# Gemini AI (receipt analysis)
|
||||
GEMINI_API_KEY=<CHANGE_ME>
|
||||
|
||||
# Nabeh Integration (must match Nabeh's .env)
|
||||
NABEH_API_KEY=<CHANGE_ME_SHARED_SECRET>
|
||||
|
||||
# Admin login
|
||||
passwordnewpassenger=<CHANGE_ME>
|
||||
allowedWallet1=Tripz-Wallet
|
||||
allowedWallet2=Intaleq-Wallet
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "root/v1",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Root\\V1\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"firebase/php-jwt": "^6.11",
|
||||
"vlucas/phpdotenv": "^5.6"
|
||||
}
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$envFile = '/home/intaleq-walletintaleq/env/.env';
|
||||
if (!file_exists($envFile)) {
|
||||
$envFile = __DIR__ . '/../.env';
|
||||
}
|
||||
loadEnvironment($envFile);
|
||||
|
||||
// Get environment variables (You don't need user/pass for JWT auth itself)
|
||||
$secretKey = getenv('SECRET_KEY'); // Only need the secret key now
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: https://wallet.siromove.com");
|
||||
header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); // Adjust as needed
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||
header('Content-Type: application/json'); // Set content type to JSON
|
||||
|
||||
// Handle preflight requests (OPTIONS)
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
|
||||
// --- Database Connection (Still needed for your application logic) ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER'); // Still used for DB connection
|
||||
$pass = getenv('PASS'); // Still used for DB connection
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
// echo $con;
|
||||
// --- JWT Authentication ---
|
||||
include "functions.php"; // Include the functions file
|
||||
|
||||
$decodedToken = authenticateJWT(); // Call the authentication function
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log($e->getMessage());
|
||||
http_response_code(500); // Internal Server Error
|
||||
echo json_encode(['error' => 'A database error occurred.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Executable
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-walletintaleq/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
|
||||
$key = getenv('keyOfApp'); // 32 bytes
|
||||
$iv = getenv('initializationVector'); // 16 bytes
|
||||
|
||||
|
||||
class EncryptionHelper {
|
||||
private $key;
|
||||
private $iv;
|
||||
|
||||
public function __construct($key, $iv) {
|
||||
if (strlen($key) !== 32) {
|
||||
throw new Exception("❌ المفتاح (Key) لازم يكون 32 بايت.");
|
||||
}
|
||||
if (strlen($iv) !== 16) {
|
||||
throw new Exception("❌ الـ IV لازم يكون 16 بايت.");
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
$this->iv = $iv;
|
||||
}
|
||||
|
||||
// --------- النصوص ----------
|
||||
private function addPadding($data, $blockSize = 16) {
|
||||
$pad = $blockSize - (strlen($data) % $blockSize);
|
||||
return $data . str_repeat(chr($pad), $pad);
|
||||
}
|
||||
|
||||
private function removePadding($data) {
|
||||
$pad = ord($data[strlen($data) - 1]);
|
||||
return substr($data, 0, -$pad);
|
||||
}
|
||||
|
||||
public function encryptData($plainText) {
|
||||
$plainText = mb_convert_encoding($plainText, 'UTF-8');
|
||||
$paddedText = $this->addPadding($plainText);
|
||||
$iv = random_bytes(16);
|
||||
$encrypted = openssl_encrypt($paddedText, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
return base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
public function decryptData($encryptedText) {
|
||||
$decoded = base64_decode($encryptedText, true);
|
||||
|
||||
if ($decoded === false) {
|
||||
error_log("[ERROR] base64_decode failed for input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
// محاولة أولى: استخراج IV عشوائي من أول 16 بايت
|
||||
if (strlen($decoded) >= 16) {
|
||||
$iv = substr($decoded, 0, 16);
|
||||
$payload = substr($decoded, 16);
|
||||
|
||||
$decrypted = openssl_decrypt($payload, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($decrypted !== false) {
|
||||
$pad = ord($decrypted[strlen($decrypted) - 1]);
|
||||
if ($pad >= 1 && $pad <= 16) {
|
||||
return substr($decrypted, 0, -$pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// محاولة ثانية: IV ثابت (للبيانات القديمة)
|
||||
$decrypted = openssl_decrypt($decoded, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
|
||||
if ($decrypted === false) {
|
||||
error_log("[ERROR] openssl_decrypt failed for input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
$pad = ord($decrypted[strlen($decrypted) - 1]);
|
||||
if ($pad < 1 || $pad > 16) {
|
||||
error_log("[ERROR] Invalid padding value ($pad) for decrypted input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
return substr($decrypted, 0, -$pad);
|
||||
}
|
||||
|
||||
public function decryptFile($encryptedFilePath, $destinationPath) {
|
||||
if (!file_exists($encryptedFilePath)) {
|
||||
throw new Exception("❌ الملف المشفر غير موجود: $encryptedFilePath");
|
||||
}
|
||||
|
||||
$encryptedData = file_get_contents($encryptedFilePath);
|
||||
$decryptedData = openssl_decrypt($encryptedData, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
|
||||
file_put_contents($destinationPath, $decryptedData);
|
||||
return true;
|
||||
}
|
||||
public function encryptBinary($data) {
|
||||
$iv = random_bytes(16);
|
||||
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
return $iv . $encrypted;
|
||||
}
|
||||
|
||||
public function decryptBinary($data) {
|
||||
if (strlen($data) >= 16) {
|
||||
$iv = substr($data, 0, 16);
|
||||
$payload = substr($data, 16);
|
||||
$decrypted = openssl_decrypt($payload, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($decrypted !== false) {
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
// للبيانات القديمة ذات IV الثابت
|
||||
$decrypted = openssl_decrypt($data, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
if ($decrypted === false) {
|
||||
error_log('[CRIT-07] openssl_decrypt failed in decryptBinary');
|
||||
throw new Exception('Decryption failed');
|
||||
}
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
// ✅ Load the key and IV from .env or use default values
|
||||
|
||||
// ✅ Ensure the lengths are correct
|
||||
//echo "Key Length: " . $key . PHP_EOL;
|
||||
//echo "IV Length: " . $iv . PHP_EOL;
|
||||
|
||||
try {
|
||||
$encryptionHelper = new EncryptionHelper($key, $iv);
|
||||
} catch (Exception $e) {
|
||||
error_log("[encrypt_decrypt] Initialization error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
?>
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Firebase\JWT\ExpiredException;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use Firebase\JWT\BeforeValidException;
|
||||
|
||||
define("MB", 1048576);
|
||||
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// authenticateJWT — دالة التحقق من التوكن
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// استبدل الدالة الموجودة في functions.php بهذه
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// طبقات التحقق بالترتيب:
|
||||
// 1. وجود الـ JWT في Authorization header
|
||||
// 2. صحة التوقيع وعدم انتهاء الصلاحية
|
||||
// 3. صحة الـ Issuer
|
||||
// 4. مطابقة بصمة الجهاز (X-Device-FP header)
|
||||
// 5. مطابقة الـ HMAC — للـ wallet فقط (X-HMAC-Auth header)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// جميع العمليات في الذاكرة — لا استعلامات DB
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function authenticateJWT(bool $isReg = false): object
|
||||
{
|
||||
$keyPath = getenv('WALLET_SECRET_KEY_PATH');
|
||||
$secretKey = '';
|
||||
if ($keyPath && file_exists($keyPath)) {
|
||||
$secretKey = trim(file_get_contents($keyPath));
|
||||
}
|
||||
if (!$secretKey) {
|
||||
$secretKey = getenv('SECRET_KEY') ?: '';
|
||||
}
|
||||
$hmacSecret = getenv('SECRET_KEY_HMAC');
|
||||
$fpPepper = getenv('FP_PEPPER');
|
||||
|
||||
error_log('[JWT_DEBUG] ── START authenticateJWT ──────────────────────────');
|
||||
error_log('[JWT_DEBUG] isReg=' . ($isReg ? 'true' : 'false'));
|
||||
error_log('[JWT_DEBUG] secretKey loaded: ' . (!empty($secretKey) ? 'YES (len=' . strlen($secretKey) . ')' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] hmacSecret loaded: ' . (!empty($hmacSecret) ? 'YES' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] fpPepper loaded: ' . (!empty($fpPepper) ? 'YES' : 'NO'));
|
||||
|
||||
if (!$secretKey || !$hmacSecret) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal server configuration error.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 1. استخراج الـ JWT ──────────────────────────────────────
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
$token = null;
|
||||
|
||||
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
|
||||
$token = $matches[1];
|
||||
}
|
||||
|
||||
error_log('[JWT_DEBUG] Authorization header present: ' . (!empty($authHeader) ? 'YES' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] Token extracted: ' . ($token ? 'YES (len=' . strlen($token) . ')' : 'NO ❌'));
|
||||
|
||||
if (!$token) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Authorization token required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 2. فك التشفير والتحقق ───────────────────────────────────
|
||||
try {
|
||||
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
|
||||
error_log('[JWT_DEBUG] JWT decode: SUCCESS ✅');
|
||||
error_log('[JWT_DEBUG] JWT payload: ' . json_encode((array)$decoded));
|
||||
|
||||
} catch (ExpiredException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: ExpiredException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Token expired']);
|
||||
exit;
|
||||
|
||||
} catch (SignatureInvalidException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: SignatureInvalidException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token signature']);
|
||||
exit;
|
||||
|
||||
} catch (BeforeValidException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: BeforeValidException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Token not yet valid']);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: Exception ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. التحقق من الـ Issuer ─────────────────────────────────
|
||||
$expectedIssuer = 'Tripz-Wallet';
|
||||
$actualIssuer = $decoded->iss ?? '(missing)';
|
||||
error_log('[JWT_DEBUG] Issuer check | expected=' . $expectedIssuer . ' | actual=' . $actualIssuer);
|
||||
|
||||
if ($actualIssuer !== $expectedIssuer) {
|
||||
error_log('[JWT_DEBUG] Issuer MISMATCH ❌');
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token issuer']);
|
||||
exit;
|
||||
}
|
||||
error_log('[JWT_DEBUG] Issuer: OK ✅');
|
||||
|
||||
// ── user_id ─────────────────────────────────────────────────
|
||||
$userId = $decoded->user_id ?? $decoded->sub ?? null;
|
||||
error_log('[JWT_DEBUG] user_id extracted: ' . ($userId ?? '(null) ❌'));
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid JWT payload']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. بصمة الجهاز ──────────────────────────────────────────
|
||||
if (!$isReg && $fpPepper) {
|
||||
$fpInToken = $decoded->fingerPrint ?? null;
|
||||
$fpHeader = $_SERVER['HTTP_X_DEVICE_FP'] ?? null;
|
||||
|
||||
error_log('[JWT_DEBUG] FP check | fpInToken=' . ($fpInToken ?? '(null)'));
|
||||
error_log('[JWT_DEBUG] FP check | X-Device-FP header=' . ($fpHeader ?? '(null)'));
|
||||
|
||||
if ($fpInToken !== null && $fpHeader !== null) {
|
||||
$expectedFp = hash('sha256', $fpHeader . $fpPepper);
|
||||
$fpMatch = hash_equals($expectedFp, $fpInToken);
|
||||
|
||||
error_log('[JWT_DEBUG] FP check | expectedFp=' . $expectedFp);
|
||||
error_log('[JWT_DEBUG] FP check | match=' . ($fpMatch ? 'YES ✅' : 'NO ❌'));
|
||||
|
||||
if (!$fpMatch) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Device mismatch | user=%s | IP=%s',
|
||||
$userId,
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Device mismatch']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
error_log('[JWT_DEBUG] FP check SKIPPED (token or header is null) ⚠️');
|
||||
}
|
||||
} else {
|
||||
error_log('[JWT_DEBUG] FP check SKIPPED | isReg=' . ($isReg ? 'true' : 'false') . ' | fpPepper=' . (!empty($fpPepper) ? 'set' : 'empty'));
|
||||
}
|
||||
|
||||
// ── 5. HMAC ─────────────────────────────────────────────────
|
||||
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
|
||||
error_log('[JWT_DEBUG] HMAC header present: ' . ($hmacHeader !== null ? 'YES' : 'NO (skip)'));
|
||||
|
||||
if ($hmacHeader !== null) {
|
||||
$expectedHmac = hash_hmac('sha256', $userId, $hmacSecret);
|
||||
$hmacMatch = hash_equals($expectedHmac, $hmacHeader);
|
||||
|
||||
error_log('[JWT_DEBUG] HMAC check | match=' . ($hmacMatch ? 'YES ✅' : 'NO ❌'));
|
||||
|
||||
if (!$hmacMatch) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] HMAC mismatch | user=%s | IP=%s',
|
||||
$userId,
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid HMAC']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
error_log('[JWT_DEBUG] ── ALL CHECKS PASSED ✅ ─────────────────────────');
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function filterRequest($requestname, $type = 'string') {
|
||||
if (isset($_POST[$requestname]) && !empty($_POST[$requestname])) {
|
||||
$value = trim($_POST[$requestname]);
|
||||
// Remove any control characters
|
||||
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
|
||||
// Remove any HTML or XML tags
|
||||
$value = strip_tags($value);
|
||||
// Escape any special characters
|
||||
$value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if ($type === 'numeric') {
|
||||
if (filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {
|
||||
return $value;
|
||||
}
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function sendWhatsAppFromServer($to, $message)
|
||||
{
|
||||
// 1) قائمة السيرفرات المتاحة
|
||||
$servers = [
|
||||
"https://botmasa.intaleq.xyz/send",//rama tah
|
||||
"https://botmasa2.intaleq.xyz/send",//shad
|
||||
// "https://bootride.intaleq.xyz/send",//shahd bus
|
||||
//"https://bot3.intaleq.xyz/send",//shahd
|
||||
//"https://whatsapp.tripz-egypt.com/send"//tripz
|
||||
];
|
||||
|
||||
// 2) اختيار عشوائي
|
||||
$url = $servers[array_rand($servers)];
|
||||
|
||||
// 3) إعداد البيانات
|
||||
$payload = [
|
||||
"to" => $to,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
// 4) تنفيذ الطلب
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => "POST",
|
||||
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json"
|
||||
],
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
// 5) تسجيل النتيجة
|
||||
if ($err) {
|
||||
error_log("[sendWhatsAppFromServer] cURL Error on $url: $err");
|
||||
return false;
|
||||
}
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//////////
|
||||
|
||||
function printFailure($message = "none")
|
||||
{
|
||||
echo json_encode(array("status" => "failure", "message" => $message));
|
||||
}
|
||||
function printSuccess($message = "none")
|
||||
{
|
||||
echo json_encode(array("status" => "success", "message" => $message));
|
||||
}
|
||||
|
||||
function result($count)
|
||||
{
|
||||
if ($count > 0) {
|
||||
printSuccess();
|
||||
} else {
|
||||
printFailure();
|
||||
}
|
||||
}
|
||||
|
||||
function sendEmail($from,$to, $title, $body)
|
||||
{
|
||||
// Sanitize $from to prevent email header injection
|
||||
$from = str_replace(["\r", "\n", "\r\n"], '', $from);
|
||||
$header = "From: $from" . "\n" . "CC: $from";
|
||||
mail($to, $title, $body, $header);
|
||||
}
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* jwtconnect.php — Unified Authentication Gateway (بوابة المصادقة الموحدة)
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY UPGRADE: هذا الملف أصبح بوابة مصادقة إجبارية.
|
||||
* كل طلب يجب أن يمر بأحد المسارات التالية:
|
||||
*
|
||||
* Path 1: S2S API Key → X-S2S-Api-Key header
|
||||
* Path 2: Payment Key → PAYMENT_KEY header
|
||||
* Path 3: Webhook Token → X-Auth-Token header
|
||||
* Path 4: Cron Key / CLI → X-Cron-Key header أو CLI execution
|
||||
* Path 5: Nabeh API Key → X-API-Key header (server-to-server من منصة نبه)
|
||||
* Path 6: JWT (default) → Authorization: Bearer <token>
|
||||
*
|
||||
* أي طلب بدون أي مصادقة → يُرفض تلقائياً من authenticateJWT()
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Load environment variables from .env file
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-wallet/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// Get environment variables (You don't need user/pass for JWT auth itself)
|
||||
$secretKey = getenv('SECRET_KEY'); // Only need the secret key now
|
||||
|
||||
// --- CORS Headers ---
|
||||
$allowedOrigins = [
|
||||
|
||||
'https://wallet.siromove.com',
|
||||
'https://wallet-syria.siromove.com',
|
||||
'https://wallet-egypt.siromove.com',
|
||||
'https://wallet-jordan.siromove.com',
|
||||
];
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowedOrigins)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
header("Access-Control-Allow-Origin: https://walletintaleq.intaleq.xyz");
|
||||
}
|
||||
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-S2S-Api-Key, PAYMENT_KEY, X-Auth-Token, X-Cron-Key, X-HMAC-Auth, X-Device-FP, X-API-Key");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Handle preflight requests (OPTIONS)
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
|
||||
// --- Database Connection ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
// --- Load Functions ---
|
||||
include "functions.php";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// UNIFIED AUTHENTICATION GATEWAY (بوابة المصادقة الموحدة)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
$authMethod = null;
|
||||
$decodedToken = null;
|
||||
|
||||
// --- Path 1: S2S API Key (server-to-server calls) ---
|
||||
$s2sKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedS2s = getenv('S2S_SHARED_KEY');
|
||||
|
||||
if (!empty($s2sKey) && !empty($expectedS2s) && hash_equals($expectedS2s, $s2sKey)) {
|
||||
$authMethod = 'S2S';
|
||||
}
|
||||
|
||||
// --- Path 2: Payment Key (transfer endpoint) ---
|
||||
if (!$authMethod) {
|
||||
$paymentKey = $_SERVER['HTTP_PAYMENT_KEY'] ?? '';
|
||||
$expectedPayment = getenv('PAYMENT_KEY');
|
||||
|
||||
if (!empty($paymentKey) && !empty($expectedPayment) && hash_equals($expectedPayment, $paymentKey)) {
|
||||
$authMethod = 'PAYMENT_KEY';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 3: Webhook Auth Token (MTN/Cliq external services) ---
|
||||
if (!$authMethod) {
|
||||
$webhookToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
$expectedWebhook = getenv('WEBHOOK_AUTH_TOKEN');
|
||||
|
||||
if (!empty($expectedWebhook) && !empty($webhookToken) && hash_equals($expectedWebhook, $webhookToken)) {
|
||||
$authMethod = 'WEBHOOK';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 4: Cron Key / CLI execution ---
|
||||
if (!$authMethod) {
|
||||
// 4a: CLI execution (php script.php directly)
|
||||
if (php_sapi_name() === 'cli' || php_sapi_name() === 'cli-server') {
|
||||
$authMethod = 'CLI';
|
||||
} else {
|
||||
// 4b: HTTP cron call with key header
|
||||
$cronKey = $_SERVER['HTTP_X_CRON_KEY'] ?? '';
|
||||
$expectedCron = getenv('CRON_KEY');
|
||||
|
||||
if (!empty($cronKey) && !empty($expectedCron) && hash_equals($expectedCron, $cronKey)) {
|
||||
$authMethod = 'CRON';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 5: Nabeh API Key (server-to-server من منصة نبه) ---
|
||||
if (!$authMethod) {
|
||||
$nabehKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedNabeh = getenv('NABEH_API_KEY');
|
||||
|
||||
if (!empty($nabehKey) && !empty($expectedNabeh) && hash_equals($expectedNabeh, $nabehKey)) {
|
||||
$authMethod = 'NABEH';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 6 (DEFAULT): JWT Authentication ---
|
||||
// إذا لم يتم التعرف على أي مسار آخر، يُفرض JWT.
|
||||
// authenticateJWT() ستُرجع 401 وتوقف التنفيذ إذا لم يكن هناك JWT صالح.
|
||||
if (!$authMethod) {
|
||||
$decodedToken = authenticateJWT();
|
||||
$authMethod = 'JWT';
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log($e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A database error occurred.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
function loadEnvironment($env_file = null) {
|
||||
|
||||
if ($env_file && file_exists($env_file)) {
|
||||
// use provided path
|
||||
} else {
|
||||
$env_file = '/home/intaleq-walletintaleq/env/.env';
|
||||
if (!file_exists($env_file)) {
|
||||
$env_file = __DIR__ . '/../.env';
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($env_file)) {
|
||||
error_log("❌ .env not found: $env_file");
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line) || strpos($line, '#') === 0) continue;
|
||||
$parts = explode('=', $line, 2);
|
||||
if (count($parts) === 2) {
|
||||
[$keyName, $value] = $parts;
|
||||
$value = trim($value, "'\"");
|
||||
putenv("$keyName=$value");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// loginWalletAdmin.php (Modified for Intaleq Admin Integration)
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-wallet/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
include "functions.php";
|
||||
|
||||
// --- استدعاء المفاتيح ---
|
||||
$secretKey = getenv('SECRET_KEY');
|
||||
$allowed1 = getenv('allowedWallet1');
|
||||
$allowed2 = getenv('allowedWallet2');
|
||||
$passwordnewpassenger = getenv('passwordnewpassenger');
|
||||
$issuer = 'Tripz-Wallet';
|
||||
$allowedAudiences = array_filter([$allowed1, $allowed2]);
|
||||
|
||||
// --- إعداد رؤوس CORS ---
|
||||
header('Content-Type: application/json');
|
||||
header("Access-Control-Allow-Origin: https://walletintaleq.intaleq.xyz"); // Wallet admin only
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP");
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- التحقق من المفاتيح ---
|
||||
if (empty($secretKey) || empty($passwordnewpassenger) || empty($allowedAudiences)) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['error' => 'Server configuration error']));
|
||||
}
|
||||
|
||||
try {
|
||||
$id = filterRequest('id') ?? '';
|
||||
$password = filterRequest('password') ?? '';
|
||||
$audience = filterRequest('aud') ?? '';
|
||||
$fingerPrint = filterRequest('fingerPrint');
|
||||
|
||||
if (empty($id) || empty($password) || empty($audience) || empty($fingerPrint)) {
|
||||
http_response_code(400);
|
||||
die(json_encode(['error' => 'Missing required parameters.']));
|
||||
}
|
||||
|
||||
if (!in_array($audience, $allowedAudiences)) {
|
||||
http_response_code(403);
|
||||
die(json_encode(['error' => 'Invalid audience']));
|
||||
}
|
||||
|
||||
// --- الاتصال بقاعدة البيانات ---
|
||||
$dbuser = getenv('USER');
|
||||
$dbpass = getenv('PASS');
|
||||
$dbname = getenv('dbname');
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
|
||||
$con = new PDO($dsn, $dbuser, $dbpass, [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]);
|
||||
|
||||
// --- التحقق من الهوية ---
|
||||
// تعديل: البحث باستخدام المعرف (id) أو البصمة (fingerprint)
|
||||
$stmt = $con->prepare("SELECT * FROM `adminUser` WHERE `username` = ? OR `device_number` = ? LIMIT 1");
|
||||
$stmt->execute([$id, $fingerPrint]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
// ⚠️ CRIT-01 FIX: إزالة backdoor الدخول بكلمة سر plaintext
|
||||
// لا يمكن الدخول إلا عبر مستخدمين مسجلين في قاعدة البيانات
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'User not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// التحقق من كلمة السر باستخدام password_verify (آمن)
|
||||
if (!password_verify($password, $user['password'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid credentials']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إنشاء JWT ---
|
||||
$payload = [
|
||||
'user_id' => $id,
|
||||
'fingerPrint' => $fingerPrint,
|
||||
'exp' => time() + 3600, // زيادة وقت الصلاحية لـ ساعة
|
||||
'iat' => time(),
|
||||
'iss' => $issuer,
|
||||
'aud' => $audience
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $secretKey, 'HS256');
|
||||
$hmac = hash_hmac('sha256', $id, getenv('SECRET_KEY_HMAC'));
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'jwt' => $jwt,
|
||||
'hmac' => $hmac,
|
||||
'expires_in' => 3600
|
||||
]);
|
||||
http_response_code(200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// HIGH-05 FIX: لا تكشف رسائل الخطأ التفصيلية
|
||||
error_log('[loginWalletAdmin] Error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'An internal error occurred. Please try again later.']);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
class GeminiAi {
|
||||
private $apiKey;
|
||||
// Updated to the requested model
|
||||
private $model = "gemini-flash-lite-latest";
|
||||
private $baseUrl = "https://generativelanguage.googleapis.com/v1beta/models/";
|
||||
|
||||
public function __construct($apiKey) {
|
||||
if (empty($apiKey)) {
|
||||
throw new Exception("Gemini API Key is missing.");
|
||||
}
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
|
||||
public function verifyPayment($invoiceNumber, $amount, $paymentMethod, $proofText, $proofImageBase64 = '') {
|
||||
$prompt = "You are a financial verifier. The user claims they transferred $amount via $paymentMethod for invoice $invoiceNumber. ";
|
||||
if (!empty($proofText)) {
|
||||
$prompt .= "Here is their proof text: '$proofText'. ";
|
||||
}
|
||||
$prompt .= "Does the provided proof clearly indicate a successful transfer of $amount? Respond ONLY with a valid JSON object: {\"verified\": true/false, \"reason\": \"your reasoning\"}.";
|
||||
|
||||
$parts = [["text" => $prompt]];
|
||||
|
||||
if (!empty($proofImageBase64)) {
|
||||
$parts[] = [
|
||||
"inline_data" => [
|
||||
"mime_type" => "image/jpeg",
|
||||
"data" => $proofImageBase64
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$reqData = [
|
||||
"contents" => [
|
||||
[
|
||||
"parts" => $parts
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$url = $this->baseUrl . $this->model . ":generateContent?key=" . $this->apiKey;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($reqData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
error_log("Gemini API Error: " . $response);
|
||||
throw new Exception("AI Verification service unavailable. Details: " . $response);
|
||||
}
|
||||
|
||||
$resData = json_decode($response, true);
|
||||
$aiText = $resData['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
||||
|
||||
// Clean AI Text (remove markdown json block if exists)
|
||||
$aiText = preg_replace('/```json|```/', '', $aiText);
|
||||
$aiResult = json_decode(trim($aiText), true);
|
||||
|
||||
if ($aiResult && isset($aiResult['verified'])) {
|
||||
return $aiResult;
|
||||
}
|
||||
|
||||
throw new Exception("Invalid response format from Gemini: " . $aiText);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
include "../../connect.php";
|
||||
|
||||
// Load the .env file and set environment variables
|
||||
$env_file = __DIR__ . '/../../.env'; // Ensure the .env file exists and is named correctly
|
||||
if (file_exists($env_file)) {
|
||||
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue; // Skip comments
|
||||
}
|
||||
putenv(trim($line));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the specific key name from the request
|
||||
$keyName = filterRequest('keyName');
|
||||
|
||||
// Fetch the specific environment variable
|
||||
$envValue = getenv($keyName);
|
||||
|
||||
// Include the specific environment key in the output
|
||||
$output = [];
|
||||
if ($keyName && $envValue !== false) {
|
||||
$output[$keyName] = $envValue;
|
||||
printSuccess($output);
|
||||
} else {
|
||||
$apiKeys = getApiKeys($con);
|
||||
if ($apiKeys) {
|
||||
printSuccess($apiKeys);
|
||||
} else {
|
||||
printFailure("No records found or invalid key name");
|
||||
}
|
||||
}
|
||||
|
||||
function getApiKeys($con) {
|
||||
$sql = "SELECT `id`, `name`, `hashed_key` FROM `api_keys`";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// --- check_status.php ---
|
||||
include "../../connect.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$invoiceNumber = filterRequest("invoice_number");
|
||||
|
||||
if (empty($invoiceNumber)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $con->prepare("SELECT status FROM click_invoices WHERE invoice_number = :invoice_number LIMIT 1");
|
||||
$stmt->execute([':invoice_number' => $invoiceNumber]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($invoice) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"invoice_status" => $invoice['status']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice not found."]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in check_status.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error."]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
// --- click_webhook_handler.php ---
|
||||
// هذا هو الـ Webhook الرئيسي الذي يستقبل إشعار تأكيد الدفع من Click
|
||||
|
||||
include "../../jwtconnect.php";
|
||||
include "./finalize_payment.php"; // تضمين ملف إتمام العملية
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// **مهم جداً: التحقق من مصدر الطلب**
|
||||
// يجب التحقق من أن هذا الطلب قادم فعلاً من Click وليس من أي طرف آخر
|
||||
// المثال التالي يستخدم مفتاح سري مشترك (Shared Secret)
|
||||
$expectedToken = trim(file_get_contents('/home/intaleq-wallet/.clickKey')); // يجب استبداله بتوكن حقيقي
|
||||
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
|
||||
if ($receivedToken !== $expectedToken) {
|
||||
http_response_code(401); // Unauthorized
|
||||
echo json_encode(["status" => "error", "message" => "Authentication failed."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// قراءة البيانات القادمة من Click (عادة تكون بصيغة JSON في الـ body)
|
||||
$json_data = file_get_contents('php://input');
|
||||
$data = json_decode($json_data, true);
|
||||
|
||||
$invoiceNumber = $data['invoice_number'] ?? null;
|
||||
$transactionId = $data['transaction_id'] ?? null;
|
||||
$paymentStatus = $data['status'] ?? null;
|
||||
|
||||
if (empty($invoiceNumber) || empty($transactionId) || $paymentStatus !== 'success') {
|
||||
http_response_code(400); // Bad Request
|
||||
echo json_encode(["status" => "error", "message" => "Missing or invalid payment data."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// 1. البحث عن الفاتورة وتحديث حالتها
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `click_invoices`
|
||||
SET `status` = 'completed', `click_transaction_id` = :transaction_id
|
||||
WHERE `invoice_number` = :invoice_number AND `status` = 'pending'"
|
||||
);
|
||||
$stmt->execute([
|
||||
':transaction_id' => $transactionId,
|
||||
':invoice_number' => $invoiceNumber
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// تم تحديث الفاتورة بنجاح، الآن نقوم بإتمام العملية
|
||||
$invoiceId = $con->lastInsertId(); // ملاحظة: هذا قد لا يعمل دائماً مع UPDATE، الأفضل جلب الـ ID
|
||||
|
||||
// جلب ID الفاتورة بعد التأكد من وجودها
|
||||
$idStmt = $con->prepare("SELECT id FROM `click_invoices` WHERE `invoice_number` = :invoice_number");
|
||||
$idStmt->execute([':invoice_number' => $invoiceNumber]);
|
||||
$invoiceRecord = $idStmt->fetch();
|
||||
$invoiceId = $invoiceRecord['id'];
|
||||
|
||||
$finalizationResult = finalizeClickPayment($con, $invoiceId);
|
||||
|
||||
if ($finalizationResult['success']) {
|
||||
$con->commit();
|
||||
echo json_encode(["status" => "success", "message" => "Transaction finalized."]);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
// يجب هنا التعامل مع الحالة التي فشل فيها الإيداع رغم نجاح الدفع
|
||||
error_log("CRITICAL: Payment received for invoice {$invoiceNumber} but finalization failed.");
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "error", "message" => "Finalization failed."]);
|
||||
}
|
||||
} else {
|
||||
// لم يتم العثور على فاتورة معلقة بهذا الرقم (ربما تمت معالجتها سابقاً)
|
||||
$con->rollBack();
|
||||
http_response_code(404);
|
||||
echo json_encode(["status" => "error", "message" => "Invoice not found or already processed."]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log("Error in click_webhook_handler.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "error", "message" => "An internal server error occurred."]);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
// --- create_cliq_invoice.php ---
|
||||
include "../../connect.php";
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$userId = filterRequest("user_id");
|
||||
$userType = filterRequest("user_type");
|
||||
$amount = filterRequest("amount");
|
||||
$cliqPhone = filterRequest("cliq_phone");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
if (empty($userId) || empty($userType) || !is_numeric($amount) || $amount <= 0 || empty($cliqPhone)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invalid input provided."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$con->beginTransaction();
|
||||
|
||||
$sel = $con->prepare("
|
||||
SELECT id, invoice_number
|
||||
FROM cliq_invoices
|
||||
WHERE user_id = :uid
|
||||
AND user_type = :utype
|
||||
AND status = 'pending'
|
||||
AND DATE(created_at) = CURRENT_DATE
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$sel->execute([
|
||||
':uid' => $userId,
|
||||
':utype' => $userType,
|
||||
]);
|
||||
$existing = $sel->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existing) {
|
||||
$upd = $con->prepare("
|
||||
UPDATE cliq_invoices
|
||||
SET amount = :amount,
|
||||
phone = :phone,
|
||||
cliq_phone = :cliq_phone,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$upd->execute([
|
||||
':amount' => $amount,
|
||||
':phone' => $phone ?: null,
|
||||
':cliq_phone' => $cliqPhone,
|
||||
':id' => $existing['id'],
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Invoice updated.",
|
||||
"invoice_number" => $existing['invoice_number'],
|
||||
"mode" => "updated"
|
||||
]);
|
||||
} else {
|
||||
$invoiceNumber = "CLIQ-" . time() . mt_rand(100, 999);
|
||||
|
||||
$ins = $con->prepare("
|
||||
INSERT INTO cliq_invoices
|
||||
(invoice_number, user_id, user_type, phone, amount, cliq_phone, status, created_at, updated_at)
|
||||
VALUES
|
||||
(:invoice_number, :user_id, :user_type, :phone, :amount, :cliq_phone, 'pending', NOW(), NOW())
|
||||
");
|
||||
$ins->execute([
|
||||
':invoice_number' => $invoiceNumber,
|
||||
':user_id' => $userId,
|
||||
':user_type' => $userType,
|
||||
':phone' => $phone ?: null,
|
||||
':amount' => $amount,
|
||||
':cliq_phone' => $cliqPhone
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
$cliqAlias = $_ENV['CLIQ_ALIAS'] ?? getenv('CLIQ_ALIAS') ?: 'siro_cliq';
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Invoice created successfully.",
|
||||
"invoice_number" => $invoiceNumber,
|
||||
"cliq_alias" => $cliqAlias,
|
||||
"mode" => "inserted"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($con && $con->inTransaction()) { $con->rollBack(); }
|
||||
error_log("Error in create_cliq_invoice.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "failure", "message" => "An internal server error occurred."]);
|
||||
}
|
||||
?>
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// --- finalize_payment.php ---
|
||||
// يحتوي على الدوال المنطقية لإضافة الرصيد للمستخدمين بعد تأكيد الدفع
|
||||
|
||||
// ملاحظة: هذا الملف لا يتم استدعاؤه مباشرة، بل يتم تضمينه في mtn_webhook_handler.php
|
||||
|
||||
/**
|
||||
* دالة مركزية لإتمام الدفع بعد التحقق منه
|
||||
* @param PDO $con اتصال قاعدة البيانات
|
||||
* @param int $invoiceId معرّف الفاتورة في جدول mtn_invoices
|
||||
* @return array نتيجة العملية
|
||||
*/
|
||||
function finalizeClickPayment(PDO $con, int $invoiceId): array
|
||||
{
|
||||
try {
|
||||
// جلب تفاصيل الفاتورة
|
||||
$stmt = $con->prepare("SELECT * FROM `cliq_invoices` WHERE id = :id AND status = 'completed' LIMIT 1");
|
||||
$stmt->execute([':id' => $invoiceId]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$invoice) {
|
||||
return ['success' => false, 'message' => 'Invoice not found or not completed.'];
|
||||
}
|
||||
|
||||
$userType = $invoice['user_type'];
|
||||
$userId = $invoice['user_id'];
|
||||
$amount = (float) $invoice['amount'];
|
||||
$paymentMethod = 'click_cash'; // تحديد طريقة الدفع
|
||||
|
||||
// تحديد ما إذا كان المستخدم سائقاً أم راكباً
|
||||
if ($userType === 'driver') {
|
||||
return finalizeForDriver($con, $userId, $amount, $paymentMethod);
|
||||
} elseif ($userType === 'passenger') {
|
||||
return finalizeForPassenger($con, $userId, $amount, $paymentMethod);
|
||||
} else {
|
||||
return ['success' => false, 'message' => 'Unknown user type.'];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Finalization Exception: " . $e->getMessage());
|
||||
return ['success' => false, 'message' => 'Finalization failed'];
|
||||
}
|
||||
}
|
||||
|
||||
// --- دوال مساعدة خاصة بالسائق ---
|
||||
function finalizeForDriver(PDO $con, int $driverId, float $amount, string $paymentMethod): array
|
||||
{
|
||||
// حساب قيمة البونص كما في الكود الأصلي
|
||||
$bonusAmount = match ((int)$amount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $amount,
|
||||
};
|
||||
|
||||
// إنشاء سجل دفع جديد والحصول على ID
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID for driver.');
|
||||
|
||||
// إضافة الرصيد لمحفظة السائق
|
||||
$stmtDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$stmtDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod) VALUES (:driverId, 'driver', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':driverId' => $driverId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet failed.');
|
||||
|
||||
return ['success' => true, 'message' => 'Driver wallets updated.'];
|
||||
}
|
||||
|
||||
function generatePaymentID(PDO $con, string $driverId, float $amount, string $method): ?string {
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
|
||||
|
||||
// --- دوال مساعدة خاصة بالراكب ---
|
||||
function finalizeForPassenger(PDO $con, string $passengerId, float $amount, string $paymentMethod): array
|
||||
{
|
||||
// حساب البونص للراكب
|
||||
$finalAmount = calculatePassengerBonus($amount);
|
||||
|
||||
// إضافة الرصيد لمحفظة الراكب
|
||||
$stmtPassenger = $con->prepare("INSERT INTO passengerWallet (passenger_id, balance) VALUES (:id, :amount) ON DUPLICATE KEY UPDATE balance = balance + :amount");
|
||||
$stmtPassenger->execute([':id' => $passengerId, ':amount' => $finalAmount]);
|
||||
if ($stmtPassenger->rowCount() === 0) throw new Exception('Update passengerWallet failed.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (passengerId, driverId, amount, paymentMethod) VALUES (:passengerId, 'passenger', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':passengerId' => $passengerId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet for passenger failed.');
|
||||
|
||||
return ['success' => true, 'message' => 'Passenger wallets updated.'];
|
||||
}
|
||||
|
||||
function calculatePassengerBonus(float $amount): float {
|
||||
if ($amount == 20000) return 20500;
|
||||
if ($amount == 40000) return 42500;
|
||||
if ($amount == 100000) return 104000;
|
||||
return $amount;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// --- query_click_invoice.php ---
|
||||
// هذا السكربت هو نقطة الـ Webhook التي سيستدعيها نظام Click
|
||||
// للاستعلام عن وجود فاتورة دفع معلقة للمستخدم قبل أن يدفع
|
||||
|
||||
include "../../jwtconnect.php"; // تأكد من أن هذا المسار صحيح
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// يمكن إضافة طبقة حماية هنا للتحقق من أن الطلب قادم من سيرفرات Click
|
||||
// مثلاً عبر التحقق من IP أو من وجود Secret Key في الـ Headers
|
||||
// --- آلية الحماية ---
|
||||
$shared_secret_key = trim(file_get_contents('/home/intaleq-wallet/.clickKey'));
|
||||
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
|
||||
if ($receivedToken !== $shared_secret_key) {
|
||||
http_response_code(401); // Unauthorized
|
||||
echo json_encode(['status' => 'error', 'message' => 'Authentication failed. Invalid or missing token.']);
|
||||
exit;
|
||||
}
|
||||
try {
|
||||
// يفترض أن Click سترسل رقم هاتف المستخدم للاستعلام عنه
|
||||
//$clickPhone = filterRequest("click_phone");
|
||||
$clickPhone = $_GET['phone_number'] ?? null;
|
||||
|
||||
if (empty($clickPhone)) {
|
||||
echo json_encode(["status" => "error", "message" => "Phone number is required."]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// البحث عن فاتورة معلقة لهذا الرقم
|
||||
$stmt = $con->prepare(
|
||||
"SELECT invoice_number, amount, user_id, user_type
|
||||
FROM `click_invoices`
|
||||
WHERE `click_phone` = :click_phone AND `status` = 'pending'
|
||||
ORDER BY `created_at` DESC LIMIT 1"
|
||||
);
|
||||
$stmt->execute([':click_phone' => $clickPhone]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($invoice) {
|
||||
// تم العثور على فاتورة، يتم إرجاع تفاصيلها لنظام Click
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"statusInvoice" => "pending",
|
||||
"invoice_number" => $invoice['invoice_number'],
|
||||
"amount" => (float) $invoice['amount'],
|
||||
"description" => "شحن نقاط في تطبيق انطلق", // وصف يظهر للمستخدم في تطبيق Click
|
||||
"biller_name" => "Intaleq App"
|
||||
|
||||
]);
|
||||
} else {
|
||||
// لا توجد فاتورة معلقة
|
||||
echo json_encode(["status" => "error", "message" => "No pending invoice found for this number."]);
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in query_click_invoice.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Internal server error."]);
|
||||
http_response_code(500);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// --- verify_payment_ai.php ---
|
||||
include "../../connect.php";
|
||||
include "./finalize_payment.php";
|
||||
include "../GeminiAi.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$json_data = file_get_contents('php://input');
|
||||
$data = json_decode($json_data, true) ?: $_POST;
|
||||
|
||||
$invoiceNumber = $data['invoice_number'] ?? '';
|
||||
$proofText = $data['proof_text'] ?? '';
|
||||
$proofImageBase64 = $data['proof_image_base64'] ?? '';
|
||||
|
||||
if (empty($invoiceNumber)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($proofText) && empty($proofImageBase64)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Proof text or image is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $con->prepare("SELECT id, amount FROM cliq_invoices WHERE invoice_number = :inv AND status = 'pending'");
|
||||
$stmt->execute([':inv' => $invoiceNumber]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$invoice) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice not found or already processed."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$amount = $invoice['amount'];
|
||||
|
||||
$geminiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?: '';
|
||||
|
||||
if (empty($geminiKey)) {
|
||||
echo json_encode(["status" => "error", "message" => "Gemini API key not configured."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment($invoiceNumber, $amount, "Cliq", $proofText, $proofImageBase64);
|
||||
|
||||
if (isset($aiResult['verified']) && $aiResult['verified'] === true) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("UPDATE cliq_invoices SET status = 'completed', updated_at = NOW() WHERE id = :id AND status = 'pending'");
|
||||
$upd->execute([':id' => $invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
$finalizationResult = finalizeClickPayment($con, $invoice['id']); // assume finalizeClickPayment exists in finalize_payment.php or rename it
|
||||
if ($finalizationResult['success']) {
|
||||
$con->commit();
|
||||
echo json_encode(["status" => "success", "message" => "Payment verified and finalized."]);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode(["status" => "error", "message" => "Verification succeeded but finalization failed."]);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode(["status" => "error", "message" => "Invoice already processed."]);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? "AI rejected the proof.";
|
||||
echo json_encode(["status" => "failure", "message" => "Verification failed: $reason"]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in cliq verify: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error occurred."]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
//ridedriverPayment/add.php
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("payment_method");
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "INSERT INTO `paymentsDriverPoints` (`amount`, `payment_method`, `driverID`)
|
||||
VALUES ('$amount', '$paymentMethod', '$driverID')";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
|
||||
$insertedID = $con->lastInsertId(); // Get the last inserted ID
|
||||
printSuccess($message = $insertedID);
|
||||
} else {
|
||||
$response = array(
|
||||
"success" => false,
|
||||
"message" => "Failed to save payment data"
|
||||
);
|
||||
echo json_encode($response);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
$sql = "DELETE FROM `paymentsDriverPoints` WHERE `id` = '$id'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
echo "Record deleted successfully";
|
||||
} else {
|
||||
// Print a failure message
|
||||
echo "Failed to delete the record";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$sql = "SELECT `id`, `amount`, `payment_method`, `driverID`, `created_at`, `updated_at`
|
||||
FROM `paymentsDriverPoints`";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
} else {
|
||||
// No records found
|
||||
echo "No records found.";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "UPDATE `paymentsDriverPoints` SET `amount` = '$amount', `payment_method` = '$paymentMethod',
|
||||
`driverID` = '$driverID' WHERE `id` = '$id'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
echo "Record updated successfully";
|
||||
} else {
|
||||
// Print a failure message
|
||||
echo "Failed to update the record";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* driverWallet/add.php — إضافة رصيد لمحفظة السائق
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY FIX:
|
||||
* - إضافة التحقق من ملكية الحساب (driverID == JWT user_id)
|
||||
* - لف العملية في Transaction ذرية
|
||||
* - استخدام FOR UPDATE لمنع Race Condition على التوكن
|
||||
* - التحقق من صحة المبلغ
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Include the database connection file (calls authenticateJWT)
|
||||
include "../../connect.php";
|
||||
|
||||
// ── 1. استخراج user_id من JWT المصادق ──────────────────────────
|
||||
$jwtUserId = $decodedToken->user_id ?? $decodedToken->sub ?? null;
|
||||
|
||||
// ── 2. استخراج والتحقق من البيانات ──────────────────────────
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$token = filterRequest("token");
|
||||
|
||||
if (empty($driverID) || !isset($amount) || empty($paymentMethod) || empty($token)) {
|
||||
printFailure("Missing required parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. التحقق من ملكية الحساب ────────────────────────────────
|
||||
// السائق المصادق يمكنه فقط إضافة رصيد لمحفظته الشخصية
|
||||
if ($jwtUserId !== null && $driverID !== $jwtUserId) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Ownership mismatch in add.php | jwt_user=%s | requested_driverID=%s | IP=%s',
|
||||
$jwtUserId, $driverID, $_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
printFailure("Forbidden: You can only modify your own wallet");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. التحقق من المبلغ ─────────────────────────────────────
|
||||
$amount = floatval($amount);
|
||||
if ($amount <= 0 || $amount > 1000000) {
|
||||
printFailure("Invalid amount");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 5. العملية الذرية ─────────────────────────────────────────
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// التحقق من التوكن مع قفل السجل (FOR UPDATE) لمنع Race Condition
|
||||
$stmt = $con->prepare("SELECT * FROM payment_tokens WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||
$stmt->execute([':token' => $token]);
|
||||
$tokenData = $stmt->fetch();
|
||||
|
||||
if ($tokenData) {
|
||||
// إدخال سجل المحفظة
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// تحديث حالة التوكن
|
||||
$stmt = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE id = :tokenID");
|
||||
$stmt->execute([':tokenID' => $tokenData['id']]);
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Invalid or already used token");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[driverWallet/add] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// Include the database connection file
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
//add300ToDriver.php
|
||||
|
||||
// Get the request parameters
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 1) ATOMIC CHECK + INSERT TO PREVENT RACE CONDITION
|
||||
// -------------------------------------------------------------
|
||||
$con->beginTransaction();
|
||||
|
||||
$check = $con->prepare("
|
||||
SELECT id
|
||||
FROM driverWallet
|
||||
WHERE driverID = :driverID AND paymentMethod = :paymentMethod
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
");
|
||||
|
||||
$check->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($check->rowCount() > 0) {
|
||||
$con->rollBack();
|
||||
printFailure("لقد تم منح هذا الدفع للسائق مسبقاً — لا يمكن تكراره.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 2) INSERT INTO driverWallet
|
||||
// -------------------------------------------------------------
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute(array(
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
));
|
||||
|
||||
$con->commit();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
|
||||
printSuccess("Record saved successfully");
|
||||
|
||||
// Notify driver
|
||||
$messageBody = "تم إضافة رصيد بقيمة $amount إلى محفظتك بنجاح.";
|
||||
// sendWhatsAppFromServer($phone, $messageBody);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 3) INSERT 30,000 POINTS FOR NEW DRIVER
|
||||
// -------------------------------------------------------------
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints`
|
||||
(`amount`, `payment_method`, `driverID`, `created_at`, `updated_at`)
|
||||
VALUES (:amount, :method, :driverID, NOW(), NOW())";
|
||||
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute(array(
|
||||
':amount' => 300,
|
||||
':method' => $paymentMethod,
|
||||
':driverID' => $driverID
|
||||
));
|
||||
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* addFromAdmin.php — إضافة رصيد من المسؤول (Admin Only)
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY FIX: كان هذا الملف بدون أي مصادقة! ❌
|
||||
* الآن يتطلب:
|
||||
* 1. JWT مصادق (عبر authenticateJWT)
|
||||
* 2. التحقق من دور المسؤول (admin role)
|
||||
* 3. التحقق من المبلغ (حد أقصى)
|
||||
* 4. تسجيل تدقيق لكل عملية (audit log)
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Include the database connection WITH JWT authentication
|
||||
include "../../connect.php";
|
||||
// connect.php calls authenticateJWT() → 5-layer security check ✅
|
||||
|
||||
// ── 1. استخراج user_id من JWT المصادق ──────────────────────────
|
||||
$adminUserId = $decodedToken->user_id ?? $decodedToken->sub ?? null;
|
||||
$adminRole = $decodedToken->role ?? null;
|
||||
|
||||
// ── 2. التحقق من صلاحيات المسؤول ────────────────────────────
|
||||
// فقط المسؤول يمكنه إضافة رصيد يدوياً
|
||||
if ($adminRole !== 'admin' && $adminRole !== 'super_admin') {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Non-admin attempted addFromAdmin | user=%s | role=%s | IP=%s',
|
||||
$adminUserId,
|
||||
$adminRole ?? '(null)',
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
printFailure("Forbidden: Admin access required");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. استخراج والتحقق من البيانات ──────────────────────────
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
if (empty($driverID) || !isset($amount) || empty($paymentMethod)) {
|
||||
printFailure("Missing required parameters: driverID, amount, paymentMethod");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. التحقق من المبلغ ─────────────────────────────────────
|
||||
$amount = floatval($amount);
|
||||
if ($amount <= 0 || $amount > 1000000) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Invalid amount in addFromAdmin | admin=%s | amount=%s | driverID=%s',
|
||||
$adminUserId, $amount, $driverID
|
||||
));
|
||||
printFailure("Invalid amount: must be between 0 and 1,000,000");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 5. العملية الذرية ─────────────────────────────────────────
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// إدخال سجل المحفظة
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID ?? ('admin_' . time() . '_' . bin2hex(random_bytes(4))),
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// ── 6. تسجيل تدقيق (Audit Log) ─────────────────────────
|
||||
$auditStmt = $con->prepare(
|
||||
"INSERT INTO `admin_audit_log` (`admin_id`, `action`, `table_name`, `record_id`, `details`)
|
||||
VALUES (:admin_id, :action, :table_name, :record_id, :details)"
|
||||
);
|
||||
$auditStmt->execute([
|
||||
':admin_id' => $adminUserId,
|
||||
':action' => 'addFromAdmin_wallet',
|
||||
':table_name' => 'driverWallet',
|
||||
':record_id' => $driverID,
|
||||
':details' => json_encode([
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'phone' => $phone,
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
], JSON_UNESCAPED_UNICODE)
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
printSuccess("Record saved successfully");
|
||||
|
||||
// إرسال إشعار واتساب للسائق
|
||||
if (!empty($phone)) {
|
||||
$messageBody = "تم إضافة رصيد بقيمة $amount إلى محفظتك بنجاح.";
|
||||
sendWhatsAppFromServer($phone, $messageBody);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[addFromAdmin] Error: " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
//addPaymentToken.php
|
||||
$driverID = filterRequest("driverID");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
// Check if required fields are present
|
||||
if ($driverID === null || $amount === null) {
|
||||
printFailure("Missing required fields: driverID and amount must be provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate a more secure token
|
||||
$token = generateSecureToken($driverID, $amount);
|
||||
|
||||
// Store the token in the database
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (?, ?, NOW(), ?)");
|
||||
|
||||
try {
|
||||
$stmt->execute([$token, $driverID, $amount]);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
printSuccess($token);
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("[addPaymentToken] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
|
||||
function generateSecureToken($driverID, $amount) {
|
||||
global $secretKey;
|
||||
// Concatenate the parameters
|
||||
$data = $driverID . $amount . time();
|
||||
|
||||
// Add the secret key from the environment variable
|
||||
$data .= $secretKey;
|
||||
|
||||
// Generate a hash
|
||||
$hash = hash('sha256', $data);
|
||||
|
||||
// Add some randomness
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
|
||||
// Combine hash and random bytes
|
||||
$token = $hash . $randomBytes;
|
||||
|
||||
// Truncate to a reasonable length (e.g., 64 characters)
|
||||
return substr($token, 0, 64);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* add_s2s_reward.php — Payment Server Endpoint
|
||||
*
|
||||
* Inserts wallet credit/debit records into driverWallet.
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$points = filterRequest("points"); // Optional raw points
|
||||
|
||||
if (empty($driverID) || empty($paymentID) || !isset($amount) || empty($paymentMethod)) {
|
||||
printFailure("Missing required parameters: driverID, paymentID, amount, paymentMethod");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// Prevent duplicate challenge claims using paymentsDriverPoints table
|
||||
if (strpos($paymentMethod, 'daily_') === 0 || strpos($paymentMethod, 'weekly_') === 0) {
|
||||
$checkSql = "SELECT id FROM paymentsDriverPoints WHERE driverID = :driver_id AND payment_method = :challenge_id AND DATE(created_at) = CURDATE() FOR UPDATE";
|
||||
$stmtCheck = $con->prepare($checkSql);
|
||||
$stmtCheck->execute([
|
||||
':driver_id' => $driverID,
|
||||
':challenge_id' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmtCheck->rowCount() > 0) {
|
||||
$con->rollBack();
|
||||
printFailure("Reward already claimed today");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// If points are provided, also insert into paymentsDriverPoints
|
||||
if (!empty($points)) {
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints` (
|
||||
`amount`,
|
||||
`payment_method`,
|
||||
`driverID`
|
||||
) VALUES (
|
||||
:points,
|
||||
:paymentMethod,
|
||||
:driverID
|
||||
);";
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute([
|
||||
':points' => $points,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':driverID' => $driverID
|
||||
]);
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("add_s2s_reward: " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
include '../../connect.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$driverID = filterRequest('driverID');
|
||||
$amount = floatval(filterRequest('amount'));
|
||||
|
||||
if (empty($driverID) || empty($amount) || $amount <= 0) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Missing required fields or invalid amount']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// 1. Fetch current budget
|
||||
$stmt = $con->prepare("SELECT SUM(amount) as diff FROM payments WHERE captain_id = :driverID FOR UPDATE");
|
||||
$stmt->execute([':driverID' => $driverID]);
|
||||
$sumRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$totalBudget = floatval($sumRow['diff']);
|
||||
|
||||
if ($totalBudget < $amount) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Insufficient budget']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Generate unique tokens
|
||||
$paymentID1 = "budget2pt_" . time() . bin2hex(random_bytes(4));
|
||||
$paymentID2 = "pt2budget_" . time() . bin2hex(random_bytes(4));
|
||||
$token1 = bin2hex(random_bytes(32));
|
||||
$token2 = bin2hex(random_bytes(32));
|
||||
|
||||
// 3. Deduct from budget (payments)
|
||||
$deductAmount = -$amount;
|
||||
$stmt = $con->prepare("INSERT INTO payments (captain_id, amount, rideId, payment_method, passengerID, token)
|
||||
VALUES (:driverID, :amount, :rideId, 'myBudget', 'myBudgetToPoint', :token)");
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':amount' => $deductAmount,
|
||||
':rideId' => $paymentID1,
|
||||
':token' => $token1
|
||||
]);
|
||||
|
||||
// 4. Add to points (paymentsDriverPoints)
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (captain_id, paymentID, amount, token, paymentMethod)
|
||||
VALUES (:driverID, :paymentID, :amount, :token, 'fromBudget')");
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID2,
|
||||
':amount' => $amount,
|
||||
':token' => $token2
|
||||
]);
|
||||
|
||||
// Commit Transaction
|
||||
$con->commit();
|
||||
|
||||
echo json_encode(['status' => 'success', 'message' => 'Budget converted to points successfully']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log('[convertBudgetToPoints] Error: ' . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred.']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
// ==========================================
|
||||
// Cron Job: Remove Duplicate Records Daily
|
||||
// Tables: driverWallet, paymentsDriverPoints
|
||||
// ==========================================
|
||||
|
||||
// Load DB Connection
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
// Function to run cleanup query
|
||||
function runCleanup($con, $deleteQuery, $tableName) {
|
||||
try {
|
||||
$stmt = $con->prepare($deleteQuery);
|
||||
$stmt->execute();
|
||||
|
||||
echo "Cleanup completed for table: $tableName\n";
|
||||
echo "Rows affected: " . $stmt->rowCount() . "\n\n";
|
||||
} catch (Exception $e) {
|
||||
echo "Error cleaning $tableName\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DELETE DUPLICATES FOR driverWallet
|
||||
// ==========================================
|
||||
|
||||
$deleteDriverWallet = "
|
||||
DELETE FROM driverWallet
|
||||
WHERE id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER(PARTITION BY driverID ORDER BY dateCreated DESC) AS rn
|
||||
FROM driverWallet
|
||||
) AS subquery
|
||||
WHERE rn = 1
|
||||
);";
|
||||
|
||||
runCleanup($con, $deleteDriverWallet, "driverWallet");
|
||||
|
||||
|
||||
// ==========================================
|
||||
// DELETE DUPLICATES FOR paymentsDriverPoints
|
||||
// ==========================================
|
||||
|
||||
$deletePaymentsPoints = "
|
||||
DELETE FROM paymentsDriverPoints
|
||||
WHERE id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER(PARTITION BY driverID ORDER BY created_at DESC) AS rn
|
||||
FROM paymentsDriverPoints
|
||||
) AS subquery
|
||||
WHERE rn = 1
|
||||
);";
|
||||
|
||||
runCleanup($con, $deletePaymentsPoints, "paymentsDriverPoints");
|
||||
|
||||
echo "Cron job completed successfully.\n";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
YEAR(`driver_orders`.`created_at`) AS `year`,
|
||||
MONTH(`driver_orders`.`created_at`) AS `month`,
|
||||
COUNT(*) AS `total_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Finished' THEN 1 ELSE 0 END) AS `completed_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Apply' THEN 1 ELSE 0 END) AS `pending_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Cancel' THEN 1 ELSE 0 END) AS `canceled_orders`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Finished' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_completed`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Apply' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_pending`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Cancel' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_canceled`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Refused' THEN 1 ELSE 0 END) AS `rejected_orders`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Refused' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_rejected`
|
||||
FROM
|
||||
`driver_orders`
|
||||
LEFT JOIN `ride` ON `ride`.`id` = `driver_orders`.`order_id`
|
||||
WHERE
|
||||
`driver_orders`.`driver_id` = '$driverID'
|
||||
AND YEAR(`driver_orders`.`created_at`) = YEAR(CURDATE())
|
||||
AND MONTH(`driver_orders`.`created_at`) = MONTH(CURDATE())
|
||||
GROUP BY
|
||||
YEAR(`driver_orders`.`created_at`),
|
||||
MONTH(`driver_orders`.`created_at`)
|
||||
ORDER BY
|
||||
`year`,
|
||||
`month`;
|
||||
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
COALESCE(dw.id, 0) AS id,
|
||||
COALESCE(dw.driverID, '0') AS driverID,
|
||||
COALESCE(dw.paymentID, '0') AS paymentID,
|
||||
COALESCE(dw.dateCreated, '1970-01-01 00:00:00') AS dateCreated,
|
||||
COALESCE(dw.amount, 0) AS amount,
|
||||
COALESCE(dw.paymentMethod, '0') AS paymentMethod,
|
||||
COALESCE(dw.dateUpdated, '1970-01-01 00:00:00') AS dateUpdated,
|
||||
COALESCE((SELECT SUM(amount) FROM driverWallet WHERE driverID = '$driverID'), 0) AS total_amount
|
||||
FROM
|
||||
driverWallet dw
|
||||
WHERE
|
||||
dw.driverID = '$driverID'
|
||||
GROUP BY
|
||||
dw.id,
|
||||
dw.driverID,
|
||||
dw.paymentID,
|
||||
dw.dateCreated,
|
||||
dw.amount,
|
||||
dw.paymentMethod,
|
||||
dw.dateUpdated
|
||||
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$driver_phone = filterRequest("driver_phone");
|
||||
|
||||
$sql = "SELECT
|
||||
`driverToken`.`token`,
|
||||
`driver`.`id`,
|
||||
`driver`.`phone`,
|
||||
`driver`.`name_arabic`as name,
|
||||
driver.national_number
|
||||
FROM
|
||||
`driverToken`
|
||||
LEFT JOIN `driver` ON `driver`.`id` = `driverToken`.`captain_id`
|
||||
WHERE
|
||||
`driver`.`phone` = '$driver_phone'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($data) {
|
||||
// Print the car location data as JSON
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
|
||||
'data' => $data
|
||||
]);
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "No car locations found");
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
`id`,
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`dateCreated`,
|
||||
`amount`,
|
||||
`paymentMethod`,
|
||||
`dateUpdated`,
|
||||
(SELECT SUM(`amount`)
|
||||
FROM `driverWallet`
|
||||
WHERE `driverID` = '$driverID'
|
||||
AND `dateCreated` >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
|
||||
) AS totalAmount
|
||||
FROM `driverWallet`
|
||||
WHERE `driverID` = '$driverID'
|
||||
AND `dateCreated` >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
|
||||
ORDER BY `dateCreated` DESC;
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
driverWallet.`id`,
|
||||
driverWallet.amount,
|
||||
driverWallet.dateCreated as created_at
|
||||
FROM
|
||||
`driverWallet`
|
||||
WHERE
|
||||
driverWallet.driverID = '$driverID' AND driverWallet.dateCreated >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
|
||||
ORDER BY
|
||||
`driverWallet`.`id`
|
||||
DESC";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* get_s2s_wallet_dashboard.php — Payment Server Endpoint
|
||||
*
|
||||
* Returns wallet metrics (challenge points, today's earnings) for a driver.
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
if (empty($driverID)) {
|
||||
printFailure("Missing required parameter: driverID");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Calculate Sum of claimed challenge points
|
||||
$stmtChallengePoints = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM `paymentsDriverPoints`
|
||||
WHERE driverID = :driver_id
|
||||
AND (payment_method LIKE 'daily_%' OR payment_method LIKE 'weekly_%')
|
||||
");
|
||||
$stmtChallengePoints->execute([':driver_id' => $driverID]);
|
||||
$challengePoints = (int)($stmtChallengePoints->fetchColumn() ?: 0);
|
||||
|
||||
// 2. Calculate Today's Earnings
|
||||
$stmtTodayEarnings = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM payments
|
||||
WHERE driverID = :driver_id
|
||||
AND DATE(created_at) = CURDATE()
|
||||
");
|
||||
$stmtTodayEarnings->execute([':driver_id' => $driverID]);
|
||||
$todayEarnings = (float)($stmtTodayEarnings->fetchColumn() ?: 0.0);
|
||||
|
||||
// 3. Calculate Total Wallet Balance
|
||||
$stmtTotalWallet = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM `driverWallet`
|
||||
WHERE driverID = :driver_id
|
||||
");
|
||||
$stmtTotalWallet->execute([':driver_id' => $driverID]);
|
||||
$totalWallet = (float)($stmtTotalWallet->fetchColumn() ?: 0.0);
|
||||
|
||||
printSuccess([
|
||||
"challengePoints" => $challengePoints,
|
||||
"todayEarnings" => $todayEarnings,
|
||||
"totalWallet" => $totalWallet
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[get_s2s_wallet_dashboard] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
// Include the database connection file
|
||||
include "../../connect.php";
|
||||
|
||||
// Get the request parameters
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$payment_amount = filterRequest("payment_amount");
|
||||
$timePromo = filterRequest("timePromo"); // Example: 'morning' or 'afternoon'
|
||||
//$createdAt = date("Y-m-d H:i:s"); // Get the current date and time
|
||||
$currentDate = date("Y-m-d"); // Current date for comparison
|
||||
|
||||
// Check if a promotion record for the same driver already exists today
|
||||
$sqlCheck = "SELECT COUNT(*) FROM `driver_promotions` WHERE `driver_id` = :driver_id AND DATE(`created_at`) = :current_date
|
||||
and timePromo=:timePromo
|
||||
";
|
||||
$stmtCheck = $con->prepare($sqlCheck);
|
||||
$stmtCheck->execute(array(
|
||||
':driver_id' => $driver_id,
|
||||
':current_date' => $currentDate
|
||||
':timePromo' =>$timePromo
|
||||
));
|
||||
|
||||
$count = $stmtCheck->fetchColumn();
|
||||
|
||||
if ($count > 0) {
|
||||
// A record exists for today, so prevent the insertion
|
||||
printFailure("A promotion record for this driver already exists for today.");
|
||||
} else {
|
||||
// No record exists for today, so insert the new promotion
|
||||
$sqlInsert = "INSERT INTO `driver_promotions` (
|
||||
`driver_id`,
|
||||
`payment_amount`,
|
||||
`timePromo`
|
||||
) VALUES (
|
||||
:driver_id,
|
||||
:payment_amount,
|
||||
:timePromo
|
||||
);";
|
||||
|
||||
// Prepare the insert statement
|
||||
$stmtInsert = $con->prepare($sqlInsert);
|
||||
$stmtInsert->execute(array(
|
||||
':driver_id' => $driver_id,
|
||||
':payment_amount' => $payment_amount,
|
||||
':timePromo' => $timePromo,
|
||||
':createdAt' => $createdAt
|
||||
));
|
||||
|
||||
// Check if the query was successful
|
||||
if ($stmtInsert->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess("Promotion record saved successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure("Failed to save promotion record");
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
// Connect to database
|
||||
include '../../connect.php';
|
||||
|
||||
// Get trip details
|
||||
$driverName = filterRequest('name');
|
||||
$driverEmail = filterRequest('email');
|
||||
$driverPhone = filterRequest('phone');
|
||||
$amount = filterRequest('amount');
|
||||
$newDriverName = filterRequest('newDriver');
|
||||
$newEmail=filterRequest('newEmail');
|
||||
|
||||
// Get language preference from database or user input
|
||||
$language = 'en'; // Default to English
|
||||
// Email content
|
||||
if ($language === 'ar') {
|
||||
$bodyEmail = "<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f8fa;
|
||||
color: #14171a;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #1da1f2;
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
a {
|
||||
color: #1da1f2;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<h1>تفاصيل نقلك على سفر</h1>
|
||||
<p>شكراً لاستخدام خدمتنا. نتمنى لك يوماً رائعاً!</p>
|
||||
<p>نريد إعلامك أن مبلغ $amount تم نقله من حسابك إلى السائق الجديد، $newDriverName (هاتف: $driverPhone).</p>
|
||||
<p>مع خالص التحية،<br> فريق سفر</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
} else {
|
||||
$bodyEmail = "<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f8fa;
|
||||
color: #14171a;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #1da1f2;
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
a {
|
||||
color: #1da1f2;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<img src='https://lh3.googleusercontent.com/a/ACg8ocLe5TgvmTjoFx7KjIoWGxX0G2ryKBTzUZi2-mBYb9DI1dsKQ0WEYh5ZPdnA3WeFbp9VnaTNzJuA0w8S4RiQ7042AKrOwXo3=s576-c-no' alt='SIRO App Logo' style='width: 150px; margin: 20px auto; display: block;'>
|
||||
|
||||
<h1>Your SIRO Transfer Details</h1>
|
||||
<p>Thank you for using our service. We hope you have a great day!</p>
|
||||
<p>We want to inform you that an amount of $amount has been transferred from your account to the new driver: $newDriverName (Phone: $driverPhone).</p>
|
||||
<p>Regards,<br> SIRO Team</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
}
|
||||
|
||||
// Email headers
|
||||
$supportEmail = 'siroteam@siro.live';
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||
$headers .= "From: $supportEmail\r\n";
|
||||
|
||||
// Send email
|
||||
if (!empty($driverEmail)) {
|
||||
if (mail($driverEmail, "Your SIRO Transfer Details", $bodyEmail, $headers)) {
|
||||
|
||||
mail($newEmail, "Your SIRO Transfer Details", $bodyEmail, $headers);
|
||||
echo "Email sent successfully.";
|
||||
} else {
|
||||
echo "Email sending failed.";
|
||||
}
|
||||
} else {
|
||||
echo "Invalid email address: $driverEmail";
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
include '../../jwtconnect.php';
|
||||
|
||||
// Disable error reporting output for production API
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
// Set header
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$senderID = filterRequest('senderID');
|
||||
$receiverID = filterRequest('receiverID'); // Now receiving the ID directly from Main Server
|
||||
$amount = floatval(filterRequest('amount'));
|
||||
$country = filterRequest('country'); // e.g. Egypt, Syria, Jordan
|
||||
|
||||
if (empty($senderID) || empty($receiverID) || empty($amount) || empty($country)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
|
||||
exit;
|
||||
}
|
||||
// --- Payment Key Authentication ---
|
||||
$expectedKey = getenv('PAYMENT_KEY');
|
||||
$providedKey = $_SERVER['HTTP_PAYMENT_KEY'] ?? '';
|
||||
|
||||
if (empty($expectedKey) || $providedKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Unauthorized Payment Server Access (Invalid Key)']);
|
||||
exit;
|
||||
}
|
||||
// 1. Determine Fee based on Country
|
||||
$fee = 0;
|
||||
if (strtolower($country) === 'egypt') {
|
||||
$fee = 5;
|
||||
if ($amount < 10) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Egypt is 10']);
|
||||
exit;
|
||||
}
|
||||
} elseif (strtolower($country) === 'syria') {
|
||||
$fee = 10;
|
||||
if ($amount < 100) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Syria is 100']);
|
||||
exit;
|
||||
}
|
||||
} elseif (strtolower($country) === 'jordan') {
|
||||
$fee = 0.25;
|
||||
if ($amount < 1) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Jordan is 1']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Default fee if unknown
|
||||
$fee = 5;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
if ($receiverID == $senderID) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Cannot transfer to yourself']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Fetch Sender Budget (with FOR UPDATE to lock rows)
|
||||
$stmt = $con->prepare("SELECT SUM(amount) as diff FROM payments WHERE captain_id = :senderID FOR UPDATE");
|
||||
$stmt->execute([':senderID' => $senderID]);
|
||||
$sumRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$totalBudget = floatval($sumRow['diff']);
|
||||
|
||||
if ($totalBudget < $amount) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Insufficient budget']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$amountForReceiver = $amount - $fee;
|
||||
if ($amountForReceiver <= 0) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Transfer amount must be greater than the fee']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Generate unique Tokens and paymentIDs
|
||||
$paymentID1 = "transfer_" . time() . bin2hex(random_bytes(4));
|
||||
$paymentID2 = "transfer_recv_" . time() . bin2hex(random_bytes(4));
|
||||
$token1 = bin2hex(random_bytes(32));
|
||||
$token2 = bin2hex(random_bytes(32));
|
||||
$siroToken = bin2hex(random_bytes(32));
|
||||
|
||||
// 4. Deduct from Sender (payments table)
|
||||
$deductAmount = -$amount;
|
||||
$stmt = $con->prepare("INSERT INTO payments (captain_id, amount, rideId, payment_method, passengerID, token)
|
||||
VALUES (:senderID, :amount, :rideId, 'cash_transfer', :receiverRef, :token)");
|
||||
$stmt->execute([
|
||||
':senderID' => $senderID,
|
||||
':amount' => $deductAmount,
|
||||
':rideId' => $paymentID1,
|
||||
':receiverRef' => 'To ' . $receiverID,
|
||||
':token' => $token1
|
||||
]);
|
||||
|
||||
// 5. Add to Receiver Points (paymentsDriverPoints table)
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (captain_id, paymentID, amount, token, paymentMethod)
|
||||
VALUES (:receiverID, :paymentID, :amount, :token, 'Transfer')");
|
||||
$stmt->execute([
|
||||
':receiverID' => $receiverID,
|
||||
':paymentID' => $paymentID2,
|
||||
':amount' => $amountForReceiver,
|
||||
':token' => $token2
|
||||
]);
|
||||
|
||||
// 6. Add Fee to Siro Wallet
|
||||
$stmt = $con->prepare("INSERT INTO siroWallet (amount, paymentMethod, passengerId, token, driverId)
|
||||
VALUES (:fee, 'payout fee', 'driver', :token, :senderID)");
|
||||
$stmt->execute([
|
||||
':fee' => $fee,
|
||||
':token' => $siroToken,
|
||||
':senderID' => $senderID
|
||||
]);
|
||||
|
||||
// Commit Transaction
|
||||
$con->commit();
|
||||
|
||||
echo json_encode(['status' => 'success', 'message' => 'Transfer completed successfully on payment server']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log('[transfer] Error: ' . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred.']);
|
||||
}
|
||||
?>
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
// هذا الملف هو نقطة النهاية بعد الدفع، ويقوم بكل عمليات التحقق وإضافة الرصيد
|
||||
// This file is the final endpoint after payment, handling all verification and balance updates.
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// -------------------------------------------------
|
||||
// دوال مساعدة لإنشاء التوكنات ومعرفات الدفع
|
||||
// Helper functions for creating tokens and payment IDs
|
||||
// -------------------------------------------------
|
||||
|
||||
/**
|
||||
* إنشاء توكن فريد لعملية المحفظة وتخزينه في قاعدة البيانات
|
||||
* Creates a unique token for a wallet transaction and stores it in the database.
|
||||
*/
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride"); // تأكد من صحة هذا الرابط
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string
|
||||
{
|
||||
global $secretKey; // يفترض أن هذا المتغير متاح من ملف الاتصال
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* تسجيل دفعة في جدول النقاط وإعادة المعرف الخاص بها
|
||||
* Logs a payment in the points table and returns its ID.
|
||||
*/
|
||||
function generatePaymentID($con, $driverId, $amount, $method): ?string
|
||||
{
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// المنطق الرئيسي للمعالجة
|
||||
// Main processing logic
|
||||
// -------------------------------------------------
|
||||
|
||||
// 1. استقبال الرقم المرجعي من الرابط
|
||||
// 1. Receive the order reference from the URL.
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
echo "<h1>خطأ: الرقم المرجعي للطلب مفقود.</h1>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. الانتظار والتأكد من وصول الـ Webhook
|
||||
// 2. Wait and verify that the webhook has updated the status.
|
||||
$payment = null;
|
||||
$max_attempts = 5; // محاولة لمدة 10 ثوانٍ - Poll for 10 seconds
|
||||
for ($attempts = 0; $attempts < $max_attempts; $attempts++) {
|
||||
// تأكد من أن اسم الجدول صحيح
|
||||
// Make sure the table name is correct.
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($payment) {
|
||||
break; // تم العثور على الدفعة الناجحة - Successful payment found
|
||||
}
|
||||
sleep(2); // الانتظار لمدة ثانيتين قبل المحاولة التالية - Wait 2 seconds before retrying
|
||||
}
|
||||
|
||||
// 3. التحقق من نتيجة البحث
|
||||
// 3. Check the polling result.
|
||||
if (!$payment) {
|
||||
echo "<h1>خطأ في تأكيد الدفع</h1><p>لم نتمكن من تأكيد دفعتك. قد تستغرق العملية بضع لحظات. يرجى التحقق من رصيدك في التطبيق لاحقاً أو التواصل مع الدعم الفني.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. Atomic status claim + wallet update (prevents double-processing)
|
||||
// 4. معالجة ذرية لمنح الرصيد — تمنع التكرار في حال التزامن
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
80 => 80.0,
|
||||
200 => 215.0,
|
||||
400 => 450.0,
|
||||
1000 => 1140.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// بدء معاملة: تحديث الحالة claim + إضافة المحافظ
|
||||
$con->beginTransaction();
|
||||
|
||||
// محاولة ذرية لـ claim المعاملة (فقط إذا كانت لا تزال status = 1)
|
||||
$claimStmt = $con->prepare("UPDATE paymentsLogSyriaDriver SET status = 2 WHERE order_ref = :ref AND status = 1");
|
||||
$claimStmt->execute([':ref' => $orderRef]);
|
||||
if ($claimStmt->rowCount() === 0) {
|
||||
$con->rollBack();
|
||||
error_log("VERIFY_RACE: Concurrent claim for OrderRef " . $orderRef);
|
||||
echo "<h1>تمت معالجة هذا الطلب مسبقاً</h1><p>الدفعة قيد المعالجة، يرجى التحقق من رصيدك في التطبيق.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate token for driver wallet.');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate token for siro wallet.');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID.');
|
||||
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Failed to insert into driverWallet.');
|
||||
|
||||
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenDriver->execute([':token' => $tokenDriver]);
|
||||
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt) VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([':driverId' => $driverId, ':passengerId' => 'driver', ':amount' => $originalAmount, ':paymentMethod' => $paymentMethod, ':token' => $tokenSiro]);
|
||||
|
||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
echo "<h1>تمت العملية بنجاح</h1><p>تمت إضافة الرصيد إلى محفظتك. يمكنك الآن العودة إلى التطبيق.</p>";
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($con->inTransaction()) { $con->rollBack(); }
|
||||
error_log("VERIFY_ERROR: " . $e->getMessage() . " | OrderRef: " . $orderRef);
|
||||
echo "<h1>حدث خطأ</h1><p>لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef) . "</p>";
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// استخدام ملف اتصال خاص بالـ Webhook لا يحتوي على أي تحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف Webhook النهائي الخاص بـ eCash (مع تسجيل إضافي للتصحيح)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// --- الإعدادات ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID');
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET');
|
||||
|
||||
// --- إعداد ملف اللوج (Log File) ---
|
||||
$log_dir = __DIR__ . '/../logs';
|
||||
$log_file = $log_dir . '/ecash_production.log';
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
// --- قراءة البيانات القادمة من eCash ---
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// --- تسجيل الـ Callback كاملاً لأغراض المراقبة ---
|
||||
file_put_contents($log_file, "--- NEW WEBHOOK ---\n" . date('Y-m-d H:i:s') . " - RAW BODY: " . $raw_body . PHP_EOL, FILE_APPEND);
|
||||
|
||||
if (!$data || !isset($data['Token'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- استخراج البيانات ---
|
||||
$isSuccess = $data['IsSuccess'] ?? false;
|
||||
$transactionNo = $data['TransactionNo'] ?? '';
|
||||
$amount = $data['Amount'] ?? '';
|
||||
$orderRef = $data['OrderRef'] ?? '';
|
||||
$receivedToken = $data['Token'];
|
||||
|
||||
// --- **تصحيح الأخطاء: بناء وتسجيل سلسلة التحقق** ---
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $transactionNo . $amount . $orderRef;
|
||||
$expectedToken = strtoupper(md5($verification_string));
|
||||
|
||||
// تسجيل السلسلة المستخدمة في التوقيع والقيم الفردية
|
||||
$debug_log = "VERIFICATION STRING: " . $verification_string . PHP_EOL;
|
||||
$debug_log .= " - Merchant ID Used: " . $ecash_merchant_id . PHP_EOL;
|
||||
$debug_log .= " - TransactionNo Used: " . $transactionNo . PHP_EOL;
|
||||
$debug_log .= " - Amount Used: " . $amount . PHP_EOL;
|
||||
$debug_log .= " - OrderRef Used: " . $orderRef . PHP_EOL;
|
||||
$debug_log .= "CALCULATED TOKEN: " . $expectedToken . PHP_EOL;
|
||||
$debug_log .= "RECEIVED TOKEN: " . $receivedToken . PHP_EOL;
|
||||
|
||||
file_put_contents($log_file, $debug_log, FILE_APPEND);
|
||||
|
||||
|
||||
// --- التحقق من صحة الـ Token ---
|
||||
if (!hash_equals($expectedToken, $receivedToken)) {
|
||||
http_response_code(401);
|
||||
file_put_contents($log_file, "TOKEN MISMATCH! Process stopped." . PHP_EOL, FILE_APPEND);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- تحديث حالة الدفعة في قاعدة البيانات ---
|
||||
file_put_contents($log_file, "TOKEN MATCH! Proceeding to update database." . PHP_EOL, FILE_APPEND);
|
||||
$payment_status = $isSuccess ? 1 : 0;
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyriaDriver` SET status = :status, updated_at = NOW() WHERE order_ref = :order_ref AND status = 2"
|
||||
);
|
||||
$stmt->execute([
|
||||
':status' => $payment_status,
|
||||
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "SUCCESS: Database updated." . PHP_EOL, FILE_APPEND);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "INFO: Order not found or already processed." . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
file_put_contents($log_file, "FATAL: Database update failed: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// هذا الملف يجب أن يستخدم ملف الاتصال الذي يتحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
// يجب استدعاء دالة التحقق هنا لضمان أن الطلب قادم من تطبيقك فقط
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف إتمام الدفع النهائي
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب من تطبيق فلاتر بعد عودة المستخدم.
|
||||
| 2. التحقق من وجود دفعة ناجحة حديثة للمستخدم في قاعدة البيانات.
|
||||
| 3. حساب المكافآت.
|
||||
| 4. استدعاء واجهات API داخلية لإضافة الرصيد إلى المحافظ.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$userId = filterRequest("userId"); // أو driverId
|
||||
$paymentMethod = filterRequest("paymentMethod") ?? 'ecash';
|
||||
|
||||
if (empty($userId)) {
|
||||
printFailure("معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// خطوة 1: البحث عن آخر دفعة ناجحة للمستخدم (تم تحديثها بواسطة الـ Webhook)
|
||||
$stmt = $con->prepare(
|
||||
"SELECT * FROM `paymentsLogSyria`
|
||||
WHERE user_id = :user_id
|
||||
AND status = 1
|
||||
AND updated_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->bindParam(':user_id', $userId, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
printFailure("لم يتم العثور على دفعة ناجحة حديثة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// خطوة 2: الحصول على المبلغ (لا يحتاج للقسمة على 100)
|
||||
$amount = $payment['amount'];
|
||||
|
||||
// خطوة 3: حساب المكافأة
|
||||
$finalAmount = calculateBonus($amount); // استخدم دالة حساب المكافآت الخاصة بك
|
||||
|
||||
$passengerId = $userId; // نفترض أن معرّف المستخدم هو نفسه معرّف الراكب
|
||||
|
||||
// --- هنا تضع نفس منطق إضافة الرصيد الذي كان في ملف payment_verify.php القديم ---
|
||||
// (مثال)
|
||||
// $token = generatePaymentToken($passengerId, $finalAmount);
|
||||
// addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
// ... إلخ
|
||||
|
||||
// --- النجاح النهائي ---
|
||||
printSuccess("تمت معالجة الدفع وتحديث الرصيد بنجاح.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Finalize Payment Error: " . $e->getMessage());
|
||||
printFailure("حدث خطأ في قاعدة البيانات أثناء إتمام العملية.");
|
||||
}
|
||||
|
||||
// --- يمكنك وضع دوال المساعدة هنا (calculateBonus, etc.) ---
|
||||
function calculateBonus($amount) {
|
||||
$result = $amount;
|
||||
if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 100) $result = 100.0;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php"; // تأكد من أن هذا الملف يحتوي على الاتصال بقاعدة البيانات ودوال المساعدة
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف بدء الدفع مع eCash
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب الدفع من تطبيق فلاتر (المبلغ ومعرّف المستخدم/السائق).
|
||||
| 2. إنشاء رابط دفع فريد وخاص ببوابة eCash.
|
||||
| 3. حساب رمز التحقق (Verification Code) المطلوب من eCash.
|
||||
| 4. تسجيل محاولة الدفع في قاعدة البيانات بحالة "قيد الانتظار".
|
||||
| 5. إعادة رابط الدفع إلى التطبيق ليتم عرضه في WebView.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- الإعدادات الرئيسية - يجب تخزينها كمتغيرات بيئة (Environment Variables) ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID'); // معرّف التاجر الخاص بك من eCash
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET'); // المفتاح السري الخاص بك من eCash
|
||||
$ecash_terminal_key = getenv('ECASH_TERMINAL_KEY'); // مفتاح المحطة الطرفية (Terminal Key) من eCash
|
||||
$ecash_checkout_url = 'https://checkout.ecash-pay.com/'; //
|
||||
$ecash_checkout_url_stage = 'https://checkout.ecash-pay.co/';//رابط بوابة الدفع
|
||||
$base_app_url = getenv('APP_BASE_URL'); // الرابط الأساسي لواجهة API الخاصة بك
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$amount = filterRequest("amount");
|
||||
$driverId = filterRequest("driverId"); // معرّف السائق أو المستخدم
|
||||
|
||||
// --- التحقق من صحة البيانات المدخلة ---
|
||||
if (empty($amount) || empty($driverId) || !is_numeric($amount) || $amount <= 0) {
|
||||
printFailure("المبلغ أو معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعداد متغيرات الدفع ---
|
||||
$currency = "SYP"; // العملة حسب متطلبات eCash
|
||||
$lang = "AR"; // لغة واجهة الدفع (AR أو EN)
|
||||
//$orderRef = uniqid($driverId . "_"); // إنشاء رقم مرجعي فريد للطلب لربطه بالمستخدم
|
||||
$orderRef = "tripz_" . $driverId . "_" . time();
|
||||
// --- إنشاء رمز التحقق (Verification Code) ---
|
||||
// هو عبارة عن MD5 لمجموعة من الحقول ويجب أن يكون بأحرف كبيرة
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($verification_string));
|
||||
|
||||
// --- تحديد روابط إعادة التوجيه والاستدعاء (Redirect & Callback) ---
|
||||
// الرابط الذي يتم توجيه المستخدم إليه بعد إتمام الدفع
|
||||
$redirectUrl = urlencode($base_app_url . "/driver/ecash_verify.php?orderRef=" . $orderRef);
|
||||
// الرابط الذي تستدعيه eCash لإبلاغ سيرفرك بنتيجة العملية (Webhook)
|
||||
$callbackUrl = urlencode($base_app_url . "/driver/ecash_webhook.php");
|
||||
|
||||
// --- بناء رابط الدفع النهائي الخاص بـ eCash ---
|
||||
$paymentUrl = "{$ecash_checkout_url}Checkout/CardCheckout" .
|
||||
"?tk=" . urlencode($ecash_terminal_key) .
|
||||
"&mid=" . urlencode($ecash_merchant_id) .
|
||||
"&vc=" . urlencode($verificationCode) .
|
||||
"&c=" . urlencode($currency) .
|
||||
"&a=" . urlencode($amount) .
|
||||
"&lang=" . urlencode($lang) .
|
||||
"&or=" . urlencode($orderRef) .
|
||||
"&ru=" . $redirectUrl .
|
||||
"&cu=" . $callbackUrl;
|
||||
|
||||
// --- تسجيل العملية المبدئية في قاعدة البيانات ---
|
||||
// هذا يساعد على تتبع الطلب وربطه بالـ callback القادم من eCash
|
||||
// نفترض أن حقل status يقبل القيم: 0=فشل، 1=نجاح، 2=قيد الانتظار
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO `paymentsLogSyriaDriver`( `user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (:user_id, :amount, 2, :order_ref,'ecash-driver', NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
':user_id' => $driverId,
|
||||
':amount' => $amount,
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $e->getMessage());
|
||||
printFailure("حدث خطأ أثناء بدء عملية الدفع. يرجى المحاولة مرة أخرى.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعادة رابط الدفع إلى تطبيق فلاتر ---
|
||||
// التطبيق سيستقبل هذا الرابط ويفتحه في WebView
|
||||
// نرسل الرابط داخل حقل 'message' كما يتوقع كود فلاتر
|
||||
printSuccess($paymentUrl);
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
// **FIX:** Corrected the path to go up three levels to find the 'vendor' directory
|
||||
require_once realpath(__DIR__ . '/../../../vendor/autoload.php');
|
||||
// **FIX:** Corrected the path to go up two levels to find 'load_env.php'
|
||||
require_once realpath(__DIR__ . '/../../load_env.php');
|
||||
|
||||
$env_file = '/home/tripz-egypt-wl/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
// --- Database Connection ONLY ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Webhook DB Connection Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal Server Error']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// --- ecash_config.php ---
|
||||
// Central configuration file for ecash, loading from a .env file.
|
||||
|
||||
// This assumes you have a function or a library (like Dotenv) to load the .env file.
|
||||
|
||||
|
||||
// --- IMPORTANT ---
|
||||
// Define the path to your .env file. Adjust if necessary.
|
||||
//$env_file_path = '/home/tripz-egypt-wl/env/.env'; // Or use realpath(__DIR__ . '/../.env');
|
||||
//loadEnvironment($env_file_path);
|
||||
require "../../jwtconnect.php";
|
||||
// --- Load ecash Credentials from Environment Variables ---
|
||||
define('ECASH_MERCHANT_ID', getenv('ECASH_MERCHANT_ID'));
|
||||
define('ECASH_MERCHANT_SECRET', getenv('ECASH_MERCHANT_SECRET'));
|
||||
define('ECASH_TERMINAL_KEY', getenv('ECASH_TERMINAL_KEY'));
|
||||
|
||||
// --- Set Mode (Staging/Live) from Environment Variable ---
|
||||
// Add ECASH_STAGING_MODE=true to your .env for testing
|
||||
$is_staging = getenv('ECASH_STAGING_MODE') === 'false';
|
||||
define('ECASH_STAGING_MODE', $is_staging);
|
||||
|
||||
// --- URLs (Automatically switch based on mode) ---
|
||||
$checkout_base_url = ECASH_STAGING_MODE ? 'https://checkout.ecash-pay.co' : 'https://checkout.ecash-pay.com';
|
||||
define('ECASH_CHECKOUT_URL', $checkout_base_url);
|
||||
|
||||
// --- Your Application URLs (Load from .env or define here) ---
|
||||
// It's best practice to also put these in your .env file.
|
||||
define('APP_BASE_URL', getenv('APP_BASE_URL')); // e.g., https://yourdomain.com/api
|
||||
define('APP_REDIRECT_URL_SUCCESS', APP_BASE_URL . '/success.php');
|
||||
define('APP_CALLBACK_URL', APP_BASE_URL . '/webhook_ecash.php'); // Use a specific webhook for ecash
|
||||
|
||||
// --- Other Settings ---
|
||||
define('ECASH_CURRENCY', 'SYP');
|
||||
define('ECASH_LANG', 'EN'); // 'EN' for English, 'AR' for Arabic
|
||||
|
||||
// --- Basic Validation ---
|
||||
if (!ECASH_MERCHANT_ID || !ECASH_MERCHANT_SECRET || !ECASH_TERMINAL_KEY) {
|
||||
http_response_code(500);
|
||||
error_log("ecash config: Missing one or more required ecash environment variables.");
|
||||
echo json_encode(['status' => 'error', 'message' => 'Payment gateway not configured correctly.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
// هذا الملف هو نقطة النهاية بعد الدفع، ويقوم بكل عمليات التحقق وإضافة الرصيد
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function showHTMLPage($type, $title, $message) {
|
||||
$color = $type === 'success' ? '#28a745' : '#dc3545';
|
||||
$icon = $type === 'success' ? '✔' : '✖';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($title) ?></title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f4f6f9;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-align: center;
|
||||
padding-top: 100px;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
background: #fff;
|
||||
padding: 40px 30px;
|
||||
margin: auto;
|
||||
max-width: 450px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
||||
animation: fadeIn 1s ease-out;
|
||||
}
|
||||
.icon {
|
||||
font-size: 64px;
|
||||
color: <?= $color ?>;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: <?= $color ?>;
|
||||
}
|
||||
p {
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
color: #555;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon"><?= $icon ?></div>
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
<p><?= htmlspecialchars($message) ?></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
showHTMLPage("error", "خطأ في الرابط", "الرقم المرجعي للطلب غير موجود.");
|
||||
}
|
||||
|
||||
$payment = null;
|
||||
$max_attempts = 5;
|
||||
for ($attempts = 0; $attempts < $max_attempts; $attempts++) {
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyria` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($payment) break;
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
if (!$payment) {
|
||||
logError("VERIFY", "لم يتم تأكيد الدفع بعد عدة محاولات", ["orderRef" => $orderRef]);
|
||||
showHTMLPage("error", "لم يتم تأكيد الدفع", "لم نتمكن من تأكيد دفعتك بعد. قد تستغرق العملية بضع لحظات. يرجى التحقق من رصيدك في التطبيق لاحقاً أو التواصل مع الدعم الفني.");
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
if (!$token) throw new Exception("فشل إنشاء توكن محفظة الراكب");
|
||||
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
if (!$walletResult || ($walletResult['status'] ?? 'fail') != "success") {
|
||||
throw new Exception("فشل إضافة الرصيد لمحفظة الراكب");
|
||||
}
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
if (!$siroToken) throw new Exception("فشل إنشاء توكن محفظة سفر");
|
||||
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
if (!$siroWalletResult || ($siroWalletResult['status'] ?? 'fail') != "success") {
|
||||
throw new Exception("فشل إضافة الرصيد لمحفظة سفر");
|
||||
}
|
||||
|
||||
logError("VERIFY", "اكتملت العملية بنجاح", ["orderRef" => $orderRef, "userId" => $userId]);
|
||||
showHTMLPage("success", "تم الدفع بنجاح", "تمت إضافة الرصيد إلى محفظتك. شكرًا لاستخدامك Intaleq.");
|
||||
} catch (Exception $e) {
|
||||
logError("VERIFY_ERROR", $e->getMessage(), ["orderRef" => $orderRef]);
|
||||
showHTMLPage("error", "حدث خطأ", "لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef));
|
||||
}
|
||||
|
||||
// --- دوال مساعدة ---
|
||||
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 200000) return 205000;
|
||||
if ($amount == 400000) return 425000;
|
||||
if ($amount == 1000000) return 1040000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// استخدام ملف اتصال خاص بالـ Webhook لا يحتوي على أي تحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف Webhook النهائي الخاص بـ eCash (مع تسجيل إضافي للتصحيح)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// --- الإعدادات ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID');
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET');
|
||||
|
||||
// --- إعداد ملف اللوج (Log File) ---
|
||||
$log_dir = __DIR__ . '/../logs';
|
||||
$log_file = $log_dir . '/ecash_production.log';
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
// --- قراءة البيانات القادمة من eCash ---
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// --- تسجيل الـ Callback كاملاً لأغراض المراقبة ---
|
||||
file_put_contents($log_file, "--- NEW WEBHOOK ---\n" . date('Y-m-d H:i:s') . " - RAW BODY: " . $raw_body . PHP_EOL, FILE_APPEND);
|
||||
|
||||
if (!$data || !isset($data['Token'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- استخراج البيانات ---
|
||||
$isSuccess = $data['IsSuccess'] ?? false;
|
||||
$transactionNo = $data['TransactionNo'] ?? '';
|
||||
$amount = $data['Amount'] ?? '';
|
||||
$orderRef = $data['OrderRef'] ?? '';
|
||||
$receivedToken = $data['Token'];
|
||||
|
||||
// --- **تصحيح الأخطاء: بناء وتسجيل سلسلة التحقق** ---
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $transactionNo . $amount . $orderRef;
|
||||
$expectedToken = strtoupper(md5($verification_string));
|
||||
|
||||
// تسجيل السلسلة المستخدمة في التوقيع والقيم الفردية
|
||||
$debug_log = "VERIFICATION STRING: " . $verification_string . PHP_EOL;
|
||||
$debug_log .= " - Merchant ID Used: " . $ecash_merchant_id . PHP_EOL;
|
||||
$debug_log .= " - TransactionNo Used: " . $transactionNo . PHP_EOL;
|
||||
$debug_log .= " - Amount Used: " . $amount . PHP_EOL;
|
||||
$debug_log .= " - OrderRef Used: " . $orderRef . PHP_EOL;
|
||||
$debug_log .= "CALCULATED TOKEN: " . $expectedToken . PHP_EOL;
|
||||
$debug_log .= "RECEIVED TOKEN: " . $receivedToken . PHP_EOL;
|
||||
|
||||
file_put_contents($log_file, $debug_log, FILE_APPEND);
|
||||
|
||||
|
||||
// --- التحقق من صحة الـ Token ---
|
||||
if (!hash_equals($expectedToken, $receivedToken)) {
|
||||
http_response_code(401);
|
||||
file_put_contents($log_file, "TOKEN MISMATCH! Process stopped." . PHP_EOL, FILE_APPEND);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- تحديث حالة الدفعة في قاعدة البيانات ---
|
||||
file_put_contents($log_file, "TOKEN MATCH! Proceeding to update database." . PHP_EOL, FILE_APPEND);
|
||||
$payment_status = $isSuccess ? 1 : 0;
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = :status, updated_at = NOW() WHERE order_ref = :order_ref AND status = 2"
|
||||
);
|
||||
$stmt->execute([
|
||||
':status' => $payment_status,
|
||||
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "SUCCESS: Database updated." . PHP_EOL, FILE_APPEND);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "INFO: Order not found or already processed." . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
file_put_contents($log_file, "FATAL: Database update failed: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// هذا الملف يجب أن يستخدم ملف الاتصال الذي يتحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
// يجب استدعاء دالة التحقق هنا لضمان أن الطلب قادم من تطبيقك فقط
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف إتمام الدفع النهائي
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب من تطبيق فلاتر بعد عودة المستخدم.
|
||||
| 2. التحقق من وجود دفعة ناجحة حديثة للمستخدم في قاعدة البيانات.
|
||||
| 3. حساب المكافآت.
|
||||
| 4. استدعاء واجهات API داخلية لإضافة الرصيد إلى المحافظ.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$userId = filterRequest("userId"); // أو driverId
|
||||
$paymentMethod = filterRequest("paymentMethod") ?? 'ecash';
|
||||
|
||||
if (empty($userId)) {
|
||||
printFailure("معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// خطوة 1: البحث عن آخر دفعة ناجحة للمستخدم (تم تحديثها بواسطة الـ Webhook)
|
||||
$stmt = $con->prepare(
|
||||
"SELECT * FROM `paymentsLogSyria`
|
||||
WHERE user_id = :user_id
|
||||
AND status = 1
|
||||
AND updated_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->bindParam(':user_id', $userId, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
printFailure("لم يتم العثور على دفعة ناجحة حديثة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// خطوة 2: الحصول على المبلغ (لا يحتاج للقسمة على 100)
|
||||
$amount = $payment['amount'];
|
||||
|
||||
// خطوة 3: حساب المكافأة
|
||||
$finalAmount = calculateBonus($amount); // استخدم دالة حساب المكافآت الخاصة بك
|
||||
|
||||
$passengerId = $userId; // نفترض أن معرّف المستخدم هو نفسه معرّف الراكب
|
||||
|
||||
// --- هنا تضع نفس منطق إضافة الرصيد الذي كان في ملف payment_verify.php القديم ---
|
||||
// (مثال)
|
||||
// $token = generatePaymentToken($passengerId, $finalAmount);
|
||||
// addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
// ... إلخ
|
||||
|
||||
// --- النجاح النهائي ---
|
||||
printSuccess("تمت معالجة الدفع وتحديث الرصيد بنجاح.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Finalize Payment Error: " . $e->getMessage());
|
||||
printFailure("حدث خطأ في قاعدة البيانات أثناء إتمام العملية.");
|
||||
}
|
||||
|
||||
// --- يمكنك وضع دوال المساعدة هنا (calculateBonus, etc.) ---
|
||||
function calculateBonus($amount) {
|
||||
$result = $amount;
|
||||
if ($amount == 500) return 530;
|
||||
if ($amount == 1000) return 1070;
|
||||
if ($amount == 2000) return 2180;
|
||||
if ($amount == 5000) return 5700;
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php"; // تأكد من أن هذا الملف يحتوي على الاتصال بقاعدة البيانات ودوال المساعدة
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف بدء الدفع مع eCash
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب الدفع من تطبيق فلاتر (المبلغ ومعرّف المستخدم/السائق).
|
||||
| 2. إنشاء رابط دفع فريد وخاص ببوابة eCash.
|
||||
| 3. حساب رمز التحقق (Verification Code) المطلوب من eCash.
|
||||
| 4. تسجيل محاولة الدفع في قاعدة البيانات بحالة "قيد الانتظار".
|
||||
| 5. إعادة رابط الدفع إلى التطبيق ليتم عرضه في WebView.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- الإعدادات الرئيسية - يجب تخزينها كمتغيرات بيئة (Environment Variables) ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID'); // معرّف التاجر الخاص بك من eCash
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET'); // المفتاح السري الخاص بك من eCash
|
||||
$ecash_terminal_key = getenv('ECASH_TERMINAL_KEY'); // مفتاح المحطة الطرفية (Terminal Key) من eCash
|
||||
$ecash_checkout_url = 'https://checkout.ecash-pay.com/'; //
|
||||
$ecash_checkout_url_stage = 'https://checkout.ecash-pay.co/';//رابط بوابة الدفع
|
||||
$base_app_url = getenv('APP_BASE_URL'); // الرابط الأساسي لواجهة API الخاصة بك
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId"); // معرّف السائق أو المستخدم
|
||||
|
||||
// --- التحقق من صحة البيانات المدخلة ---
|
||||
if (empty($amount) || empty($passengerId) || !is_numeric($amount) || $amount <= 0) {
|
||||
printFailure("المبلغ أو معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعداد متغيرات الدفع ---
|
||||
$currency = "SYP"; // العملة حسب متطلبات eCash
|
||||
$lang = "AR"; // لغة واجهة الدفع (AR أو EN)
|
||||
//$orderRef = uniqid($passengerId . "_"); // إنشاء رقم مرجعي فريد للطلب لربطه بالمستخدم
|
||||
$orderRef = "tripz_" . $passengerId . "_" . time();
|
||||
// --- إنشاء رمز التحقق (Verification Code) ---
|
||||
// هو عبارة عن MD5 لمجموعة من الحقول ويجب أن يكون بأحرف كبيرة
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($verification_string));
|
||||
|
||||
// --- تحديد روابط إعادة التوجيه والاستدعاء (Redirect & Callback) ---
|
||||
// الرابط الذي يتم توجيه المستخدم إليه بعد إتمام الدفع
|
||||
$redirectUrl = urlencode($base_app_url . "/passenger/ecash_verify.php?orderRef=" . $orderRef);
|
||||
// الرابط الذي تستدعيه eCash لإبلاغ سيرفرك بنتيجة العملية (Webhook)
|
||||
$callbackUrl = urlencode($base_app_url . "/passenger/ecash_webhook.php");
|
||||
|
||||
// --- بناء رابط الدفع النهائي الخاص بـ eCash ---
|
||||
$paymentUrl = "{$ecash_checkout_url}Checkout/CardCheckout" .
|
||||
"?tk=" . urlencode($ecash_terminal_key) .
|
||||
"&mid=" . urlencode($ecash_merchant_id) .
|
||||
"&vc=" . urlencode($verificationCode) .
|
||||
"&c=" . urlencode($currency) .
|
||||
"&a=" . urlencode($amount) .
|
||||
"&lang=" . urlencode($lang) .
|
||||
"&or=" . urlencode($orderRef) .
|
||||
"&ru=" . $redirectUrl .
|
||||
"&cu=" . $callbackUrl;
|
||||
//error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $paymentUrl);
|
||||
// --- تسجيل العملية المبدئية في قاعدة البيانات ---
|
||||
// هذا يساعد على تتبع الطلب وربطه بالـ callback القادم من eCash
|
||||
// نفترض أن حقل status يقبل القيم: 0=فشل، 1=نجاح، 2=قيد الانتظار
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO `paymentsLogSyria`( `user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (:user_id, :amount, 2, :order_ref,'ecash-passenger', NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
':user_id' => $passengerId,
|
||||
':amount' => $amount,
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $e->getMessage());
|
||||
printFailure("حدث خطأ أثناء بدء عملية الدفع. يرجى المحاولة مرة أخرى.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعادة رابط الدفع إلى تطبيق فلاتر ---
|
||||
// التطبيق سيستقبل هذا الرابط ويفتحه في WebView
|
||||
// نرسل الرابط داخل حقل 'message' كما يتوقع كود فلاتر
|
||||
printSuccess($paymentUrl);
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
// **FIX:** Corrected the path to go up three levels to find the 'vendor' directory
|
||||
require_once realpath(__DIR__ . '/../../../vendor/autoload.php');
|
||||
// **FIX:** Corrected the path to go up two levels to find 'load_env.php'
|
||||
require_once realpath(__DIR__ . '/../../load_env.php');
|
||||
|
||||
$env_file = '/home/tripz-egypt-wl/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
// --- Database Connection ONLY ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Webhook DB Connection Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal Server Error']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// --- payWithEcash.php (Updated) ---
|
||||
// This script now saves transaction details before generating the payment link.
|
||||
|
||||
require "../../jwtconnect.php"; // Your existing connection/auth script
|
||||
require_once "ecash_config.php"; // The ecash config file
|
||||
|
||||
// --- Get Input Data ---
|
||||
$amount = filterRequest("amount", "numeric");
|
||||
$passengerId = filterRequest("passengerId"); // Get passengerId from the request
|
||||
|
||||
if (!$amount || $amount <= 0) {
|
||||
printFailure("Invalid or missing amount.");
|
||||
exit;
|
||||
}
|
||||
if (!$passengerId) {
|
||||
printFailure("Passenger ID is required.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// The user ID from your JWT authentication in jwtconnect.php
|
||||
$userId = $decodedToken->user_id ?? null;
|
||||
if (!$userId) {
|
||||
printFailure("Authentication failed.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. --- Create a unique order reference ---
|
||||
$orderRef = 'INTALEQ_' . $userId . '_' . time();
|
||||
|
||||
// 2. --- Save the initial transaction to your database ---
|
||||
// This step is CRITICAL for the webhook to work correctly.
|
||||
// Create a table named 'ecash_transactions' with columns like:
|
||||
// id, order_ref, user_id, passenger_id, amount, status, created_at, updated_at
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO ecash_transactions (order_ref, user_id, passenger_id, amount, status) VALUES (?, ?, ?, ?, 'pending')"
|
||||
);
|
||||
$stmt->execute([$orderRef, $userId, $passengerId, $amount]);
|
||||
} catch (PDOException $e) {
|
||||
// Log the database error
|
||||
error_log("ecash_initiate DB Error: " . $e->getMessage());
|
||||
printFailure("Failed to initiate payment transaction.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. --- Generate the Verification Code (VC) ---
|
||||
$stringToHash = ECASH_MERCHANT_ID . ECASH_MERCHANT_SECRET . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($stringToHash));
|
||||
|
||||
// 4. --- Construct URLs ---
|
||||
$redirectUrl = urlencode(APP_REDIRECT_URL_SUCCESS);
|
||||
$callbackUrl = urlencode(APP_CALLBACK_URL);
|
||||
|
||||
// 5. --- Build the Final Checkout URL ---
|
||||
$checkoutUrl = sprintf(
|
||||
"%s/Checkout/CardCheckout?tk=%s&mid=%s&vc=%s&c=%s&a=%s&lang=%s&or=%s&ru=%s&cu=%s",
|
||||
ECASH_CHECKOUT_URL,
|
||||
ECASH_TERMINAL_KEY,
|
||||
ECASH_MERCHANT_ID,
|
||||
$verificationCode,
|
||||
ECASH_CURRENCY,
|
||||
$amount,
|
||||
ECASH_LANG,
|
||||
$orderRef,
|
||||
$redirectUrl,
|
||||
$callbackUrl
|
||||
);
|
||||
|
||||
// 6. --- Return the URL to Flutter ---
|
||||
printSuccess($checkoutUrl);
|
||||
|
||||
?>
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
|
||||
<?php
|
||||
// --- webhook_ecash.php ---
|
||||
// This script securely handles the callback from ecash and updates user wallets.
|
||||
|
||||
// Include necessary files
|
||||
require_once "../../jwtconnect.php"; // Adjust path as needed
|
||||
require_once "ecash_config.php"; // Adjust path as needed
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/ecash_webhook.log");
|
||||
|
||||
// --- Start Webhook Processing ---
|
||||
|
||||
// 1. Log the raw incoming data from ecash
|
||||
$raw_post_data = file_get_contents('php://input');
|
||||
logError("0", "Webhook received", ["payload" => $raw_post_data]);
|
||||
|
||||
$data = json_decode($raw_post_data, true);
|
||||
if (!$data) {
|
||||
logError("0.1", "Invalid JSON payload.");
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Extract data and verify the token from ecash
|
||||
$isSuccess = $data['isSuccess'] ?? null;
|
||||
$orderRef = $data['orderRef'] ?? null;
|
||||
$transactionNo = $data['transactionNo'] ?? null;
|
||||
$amount = $data['amount'] ?? null;
|
||||
$receivedToken = $data['token'] ?? '';
|
||||
|
||||
$string_to_hash = ECASH_MERCHANT_ID . ECASH_MERCHANT_SECRET . $transactionNo . $amount . $orderRef;
|
||||
$expected_token = md5($string_to_hash);
|
||||
|
||||
if (strcasecmp($expected_token, $receivedToken) !== 0) {
|
||||
logError("1", "Token Mismatch", [
|
||||
"expected" => $expected_token,
|
||||
"received" => $receivedToken,
|
||||
"string" => $string_to_hash
|
||||
]);
|
||||
http_response_code(401); // Unauthorized
|
||||
exit;
|
||||
}
|
||||
logError("1", "Token Verified Successfully.");
|
||||
|
||||
// 3. Check if payment was successful
|
||||
if ($isSuccess !== true) {
|
||||
logError("2", "Payment was not successful according to ecash.", $data);
|
||||
// Optionally, update your database to mark the order as 'failed'
|
||||
updateTransactionStatus($orderRef, 'failed', $transactionNo);
|
||||
http_response_code(200); // Respond OK to ecash, but do nothing else
|
||||
exit;
|
||||
}
|
||||
logError("2", "Payment reported as SUCCESS by ecash.");
|
||||
|
||||
// 4. Find and process the transaction atomically
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM ecash_transactions WHERE order_ref = ? LIMIT 1 FOR UPDATE");
|
||||
$stmt->execute([$orderRef]);
|
||||
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$transaction) {
|
||||
$con->rollBack();
|
||||
logError("3", "OrderRef not found in our database.", ["orderRef" => $orderRef]);
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($transaction['status'] !== 'pending') {
|
||||
$con->rollBack();
|
||||
logError("3.1", "Transaction already processed.", ["orderRef" => $orderRef, "status" => $transaction['status']]);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Atomically mark as processing to prevent concurrent webhooks
|
||||
$lockStmt = $con->prepare("UPDATE ecash_transactions SET status = 'processing' WHERE order_ref = ? AND status = 'pending'");
|
||||
$lockStmt->execute([$orderRef]);
|
||||
if ($lockStmt->rowCount() === 0) {
|
||||
$con->rollBack();
|
||||
logError("3.2", "Concurrent webhook detected, transaction already claimed.", ["orderRef" => $orderRef]);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerId = $transaction['passenger_id'];
|
||||
$paidAmount = $transaction['amount'];
|
||||
logError("3", "Transaction found in DB.", ["passengerId" => $passengerId, "amount" => $paidAmount]);
|
||||
|
||||
$finalAmount = calculateBonus($paidAmount);
|
||||
logError("4", "Bonus calculated.", ["original" => $paidAmount, "final" => $finalAmount]);
|
||||
|
||||
$passengerToken = generatePaymentToken($passengerId, $finalAmount);
|
||||
if ($passengerToken) {
|
||||
addToPassengerWallet($passengerId, $finalAmount, $passengerToken);
|
||||
}
|
||||
|
||||
$paymentMethod = 'ecash';
|
||||
addToSiroWallet($passengerId, $paidAmount, $paymentMethod);
|
||||
|
||||
$stmtUpdate = $con->prepare("UPDATE ecash_transactions SET status = 'success', ecash_transaction_no = ?, updated_at = NOW() WHERE order_ref = ?");
|
||||
$stmtUpdate->execute([$transactionNo, $orderRef]);
|
||||
|
||||
$con->commit();
|
||||
logError("7", "Process completed successfully.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
logError("DB_ERROR", "Database error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
logError("GENERAL_ERROR", "General error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 7. Respond to ecash server
|
||||
http_response_code(200);
|
||||
echo "Webhook processed.";
|
||||
|
||||
|
||||
// --- ALL HELPER FUNCTIONS FROM paymet_verfy.php ---
|
||||
|
||||
function updateTransactionStatus($orderRef, $status, $transactionNo) {
|
||||
global $con;
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE ecash_transactions SET status = ?, ecash_transaction_no = ?, updated_at = NOW() WHERE order_ref = ?"
|
||||
);
|
||||
$stmt->execute([$status, $transactionNo, $orderRef]);
|
||||
} catch (PDOException $e) {
|
||||
logError("DB_UPDATE_ERROR", "Failed to update transaction status", ["error" => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
|
||||
$postData = [
|
||||
'passengerId' => $passengerId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("4.1", "cURL error in token generation", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("4.2", "HTTP error in token generation", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !isset($data['message'])) {
|
||||
logError("4.3", "Invalid response format in token generation", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data['message']; // ✅ Return token
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to passenger's wallet with error logging
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'passenger_id' => $passengerId,
|
||||
'balance' => $amount,
|
||||
'token' => $token
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("5.1", "cURL error in passenger wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("5.2", "HTTP error in passenger wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("5.3", "Invalid response format in passenger wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to Siro wallet with error logging
|
||||
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod) {
|
||||
|
||||
|
||||
// Generate a new token specifically for the Siro wallet
|
||||
$siroToken = generatePaymentToken($passengerId, $amount);
|
||||
|
||||
if (!$siroToken) {
|
||||
logError("6.0.1", "Failed to generate Siro token");
|
||||
return null;
|
||||
}
|
||||
|
||||
logError("6.0.2", "Generated new Siro token", [
|
||||
"token_length" => ($siroToken)
|
||||
]);
|
||||
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $siroToken, // Use the new Siro-specific token
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("6.1", "cURL error in Siro wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("6.2", "HTTP error in Siro wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("6.3", "Invalid response format in Siro wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
|
||||
// 🎯 Function to calculate bonus
|
||||
function calculateBonus($amount) {
|
||||
logError("3.1", "Bonus calculation input", ["amount" => $amount]);
|
||||
|
||||
$result = 0;
|
||||
if ($amount == 100) $result = 100;
|
||||
else if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
|
||||
logError("3.2", "Bonus calculation result", [
|
||||
"input" => $amount,
|
||||
"output" => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/driver/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// It's better to use __DIR__ for reliable file path resolution.
|
||||
// Assuming your private_key.pem is in the same directory as this script.
|
||||
$privateKeyPath = __DIR__ . "/private_key.pem";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents($privateKeyPath));
|
||||
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // The OTP
|
||||
|
||||
error_log("MTN Confirm (Driver): Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
error_log("MTN Confirm (Driver): Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Encrypt the code
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
$body = [
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => intval($operationNumber),
|
||||
'Code' => $codeB64
|
||||
];
|
||||
$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
error_log("MTN Confirm (Driver): Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// Generate signature
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult) {
|
||||
error_log("MTN Confirm (Driver): Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
error_log("MTN Confirm (Driver): Generated signature");
|
||||
|
||||
// Send the request
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("MTN Confirm (Driver): HTTP $httpCode - Response: $response");
|
||||
if ($curlError) {
|
||||
error_log("MTN Confirm (Driver): cURL error - $curlError");
|
||||
}
|
||||
|
||||
// --- SOLUTION IMPLEMENTED HERE ---
|
||||
// 1. Decode the response from MTN to check its contents.
|
||||
$responseData = json_decode($response, true) ?: [];
|
||||
|
||||
// 2. First, check for network/gateway level failure.
|
||||
if ($httpCode !== 200) {
|
||||
error_log("MTN Confirm (Driver): HTTP failure for invoice {$invoice}. Code: {$httpCode}");
|
||||
// Use printFailure to send a structured error to Flutter
|
||||
printFailure(['message' => 'MTN Gateway Error', 'http' => $httpCode, 'details' => $responseData]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Now, check for business-logic failure (like "Balance not enough").
|
||||
$errno = $responseData['Errno'] ?? -1; // Default to an error state if Errno is missing
|
||||
if ($errno !== 0) {
|
||||
$apiError = $responseData['Error'] ?? 'Unknown MTN API Error';
|
||||
error_log("MTN Confirm (Driver): Business failure for invoice {$invoice}. Errno: {$errno}, Reason: {$apiError}");
|
||||
// This now sends the specific error message in the format Flutter expects!
|
||||
printFailure(['message' => $apiError, 'errno' => $errno, 'details' => $responseData]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- ONLY PROCEED TO DATABASE ON FULL SUCCESS (HTTP 200 AND Errno 0) ---
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyriaDriver` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
error_log("MTN Confirm (Driver): Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
// The file path correction from before remains important.
|
||||
include_once __DIR__ . '/finalize_wallet_payment.php';
|
||||
|
||||
// Call the wallet finalization logic
|
||||
if (function_exists('finalizeWalletPayment')) {
|
||||
$_GET['orderRef'] = $invoice;
|
||||
finalizeWalletPayment($con);
|
||||
} else {
|
||||
error_log("MTN Confirm (Driver): FATAL - finalizeWalletPayment() function does not exist after include.");
|
||||
}
|
||||
|
||||
// On success, send a success response to Flutter
|
||||
printSuccess(['message' => 'Payment confirmed successfully', 'details' => $responseData]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("MTN Confirm (Driver): DB update error - " . $e->getMessage());
|
||||
printFailure("Database processing error.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// wallet/finalize_wallet_payment.php
|
||||
include_once "../../../jwtconnect.php";
|
||||
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $method): ?string {
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
|
||||
function finalizeWalletPayment($con) {
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
logError("FINALIZE", "Missing orderRef");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. تحقق من الدفع
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("FINALIZE", "Payment not found or not completed", ['orderRef' => $orderRef]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
// حساب المكافأة
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// إنشاء التوكنات
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate driver token');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate siro token');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID');
|
||||
|
||||
// driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonusAmount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed');
|
||||
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenDriver]);
|
||||
|
||||
// siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenSiro]);
|
||||
|
||||
logError("FINALIZE", "Wallets updated successfully", ['orderRef' => $orderRef]);
|
||||
printSuccess("FINALIZE", "Wallets updated successfully");
|
||||
} catch (Throwable $e) {
|
||||
logError("FINALIZE", "Exception during finalization: " . $e->getMessage(), ['orderRef' => $orderRef]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// File: generate_keys.php
|
||||
// الوظيفة: إنشاء زوج المفاتيح (العام والخاص) لمرة واحدة فقط
|
||||
|
||||
// إعدادات لتوليد المفتاح
|
||||
$config = [
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 1024,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
// إنشاء زوج المفاتيح
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if (!$res) {
|
||||
die('Failed to generate new private key. Error: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// استخراج المفتاح الخاص
|
||||
openssl_pkey_export($res, $private_key);
|
||||
|
||||
// استخراج المفتاح العام
|
||||
$public_key_details = openssl_pkey_get_details($res);
|
||||
$public_key = $public_key_details["key"];
|
||||
|
||||
// حفظ المفاتيح في ملفات
|
||||
file_put_contents('private_key.pem', $private_key);
|
||||
file_put_contents('public_key.pem', $public_key);
|
||||
|
||||
echo "<h1>Keys Generated Successfully!</h1>";
|
||||
echo "<h2>Private Key (saved to private_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($private_key) . "</pre>";
|
||||
echo "<h2>Public Key (saved to public_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($public_key) . "</pre>";
|
||||
|
||||
// --- تحضير المفتاح العام لعملية التفعيل ---
|
||||
// إزالة الهيدر والفوتر والأسطر الجديدة كما هو مطلوب
|
||||
$formatted_public_key = str_replace("-----BEGIN PUBLIC KEY-----", "", $public_key);
|
||||
$formatted_public_key = str_replace("-----END PUBLIC KEY-----", "", $formatted_public_key);
|
||||
$formatted_public_key = preg_replace("/\s+/", "", $formatted_public_key);
|
||||
|
||||
|
||||
echo "<h2>Formatted Public Key (for Terminal Activation):</h2>";
|
||||
echo "<p><strong>انسخ هذا المفتاح لاستخدامه في خطوة تفعيل الجهاز (activate_terminal.php)</strong></p>";
|
||||
echo "<textarea rows='5' cols='80' readonly>" . htmlspecialchars($formatted_public_key) . "</textarea>";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/initiate_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
|
||||
$invoice = filterRequest('invoice'); // رقم الفاتورة
|
||||
$phone = filterRequest('phone'); // رقم الزبون
|
||||
$guid = uniqid('mtn_');
|
||||
|
||||
if (!$invoice || !$phone) {
|
||||
printFailure("Missing invoice or phone.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = json_encode([
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid
|
||||
], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$hash = hash('sha256', $body, true);
|
||||
$pkey = openssl_get_privatekey($privateKeyPem);
|
||||
if (!$pkey) {
|
||||
error_log("[MTN Initiate] Failed to load private key");
|
||||
printFailure("Payment configuration error");
|
||||
exit;
|
||||
}
|
||||
openssl_sign($hash, $sig, $pkey, OPENSSL_ALGO_SHA256);
|
||||
openssl_free_key($pkey);
|
||||
$xSignature = base64_encode($sig);
|
||||
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/initiate");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/initiate",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// سجل المحاولة مع Guid
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `mtn_payments`
|
||||
SET guid = :guid, status = 3, updated_at = NOW()
|
||||
WHERE invoice = :inv"
|
||||
);
|
||||
$stmt->execute([':guid'=>$guid, ':inv'=>$invoice]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// بيانات التفعيل
|
||||
$terminalId = "9001000000060863";
|
||||
$activationCode = "26164711";
|
||||
$serialNumber = "INTALEQ-001"; // يمكنك تغييره
|
||||
|
||||
// المفتاح العام على سطر واحد — بدون BEGIN/END وبدون أسطر جديدة
|
||||
//$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNxFbepx2OrpyrNG4+/aAaH3Rjc8dGw6B6vMAfsZzzm4wzoSkrtsr6jfuaMTZRLwxS5h8k1ztLG1HrOmL/NDsiE/7yxaKLAIZyWB/rR9byvPeOCC8QnCd/08kmxNl/l7Akn6qlPwsVpKUUNsr0SkU9lShMAw4OBgQq399jsbkFSwIDAQAB";
|
||||
$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOhVAdUyxFpVNSyjRndMWEPAN9vJEetMzLbjF9DTn2lPVuRj/Mkwq9wCNhy+tdeX2lIn4K3EkONBvYJubBhxnYOoQuMchPW5vG7VnmpLjZ7TkpM2n9fcMu8u1GkLatLblDI4LTfvn3851+nhpnYlUVkjw5GAhH4XnEpveIjqDhzQIDAQAB";
|
||||
// جسم الطلب
|
||||
$body = [
|
||||
"Key" => $publicKey,
|
||||
"Secret" => $activationCode,
|
||||
"Serial" => $serialNumber
|
||||
];
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES);
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)),'"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// 1. توليد هاش SHA256 للـ JSON
|
||||
//$bodyHash = hash('sha256', $bodyJson, true);
|
||||
|
||||
// 2. تحميل المفتاح الخاص للتوقيع
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem")); // تأكد من وجود هذا الملف بجانب السكربت
|
||||
|
||||
// 3. توقيع الهاش
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
|
||||
// 4. تحويل التوقيع إلى Base64
|
||||
$xSignature = base64_encode($signature);
|
||||
|
||||
// 5. إرسال الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: pos_web/pos/activate",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init("https://cashmobile.mtnsyr.com:9000");
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// ✅ النتيجة
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
"httpCode" => $httpCode,
|
||||
"response" => json_decode($response, true),
|
||||
"sentBody" => $body,
|
||||
]);
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
date_default_timezone_set("Asia/Damascus");
|
||||
|
||||
// ========== إعدادات MTN ==========
|
||||
$terminalId = "9001000000060863";
|
||||
$currencyCode = 760;
|
||||
$sessionNumber = 0;
|
||||
$ttl = 15;
|
||||
|
||||
// ====== استقبال البيانات من فلاتر ======
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
// ✅ Log مبدئي
|
||||
error_log("🚦 START | passengerId: $passengerId | phone: $phone | amount: $amount");
|
||||
|
||||
// تحقق من المدخلات
|
||||
if (empty($amount) || empty($passengerId) || empty($phone) || $amount <= 0) {
|
||||
error_log("❌ Invalid input: amount=$amount, passengerId=$passengerId, phone=$phone");
|
||||
printFailure("بيانات الدفع غير كاملة أو غير صالحة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== توليد invoiceNumber و GUID ======
|
||||
$invoiceNumber = mt_rand(10000000000, 99999999999);
|
||||
//$invoiceNumber = "MTN_" . $passengerId . "_" . time();
|
||||
$guid = uniqid("mtn_");
|
||||
error_log("🧾 Generated Invoice: $invoiceNumber");
|
||||
error_log("🧭 Generated GUID: $guid");
|
||||
|
||||
// ====== 1. إنشاء الفاتورة ======
|
||||
$createInvoiceBody = [
|
||||
"Amount" => intval($amount * 100),
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Session" => $sessionNumber,
|
||||
"TTL" => $ttl
|
||||
];
|
||||
error_log("📦 Create Invoice Body: " . json_encode($createInvoiceBody, JSON_UNESCAPED_UNICODE));
|
||||
$invoiceResponse = sendMtnApiRequest("pos_web/invoice/create", $terminalId, $createInvoiceBody);
|
||||
error_log("📥 Create Invoice Response: " . json_encode($invoiceResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$invoiceResponse || isset($invoiceResponse['Errno']) && $invoiceResponse['Errno'] != 0) {
|
||||
error_log("❌ Failed to create invoice. Error: " . json_encode($invoiceResponse));
|
||||
printFailure("فشل إنشاء الفاتورة عبر MTN.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 2. بدء الدفع ======
|
||||
$initiateBody = [
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Phone" => $phone,
|
||||
"Guid" => $guid
|
||||
];
|
||||
error_log("📤 body initiateBody: $initiateBody");
|
||||
error_log("📦 Initiate Payment Body: " . json_encode($initiateBody, JSON_UNESCAPED_UNICODE));
|
||||
$initiateResponse = sendMtnApiRequest("pos_web/payment_phone/initiate", $terminalId, $initiateBody);
|
||||
error_log("📥 Initiate Response: " . json_encode($initiateResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$initiateResponse || !isset($initiateResponse['OperationNumber'])) {
|
||||
error_log("❌ Failed to initiate payment.");
|
||||
printFailure($initiateResponse);
|
||||
exit;
|
||||
}
|
||||
|
||||
$operationNumber = $initiateResponse['OperationNumber'];
|
||||
|
||||
// ====== 3. تسجيل العملية ======
|
||||
try {
|
||||
$stmt = $con->prepare("INSERT INTO `paymentsLogSyriaDriver`
|
||||
(`user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (?, ?, 2, ?, 'mtn', NOW())");
|
||||
$stmt->execute([$passengerId, $amount, $invoiceNumber]);
|
||||
error_log("✅ DB Log Inserted.");
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ DB ERROR: " . $e->getMessage());
|
||||
printFailure("فشل في تسجيل العملية.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 4. نجاح
|
||||
error_log("✅ Payment initiation successful.");
|
||||
printSuccess([
|
||||
"invoiceNumber" => $invoiceNumber,
|
||||
"operationNumber" => $operationNumber,
|
||||
"guid" => $guid
|
||||
]);
|
||||
|
||||
|
||||
// ====== دالة إرسال الطلب =====================
|
||||
function sendMtnApiRequest($requestName, $terminalId, $body)
|
||||
{
|
||||
$apiUrl = "https://cashmobile.mtnsyr.com:9000";
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
|
||||
// ✅ تحويل الـ body إلى JSON بدون فراغات أو أسطر
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// ✅ توليد التوقيع
|
||||
// $bodyHash = hash('sha256', $bodyJson, true);
|
||||
error_log("📤 body before JSON: $bodyJson");
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
$xSignature = base64_encode($signature);
|
||||
error_log("📤 body xSignature: $xSignature");
|
||||
// ✅ رؤوس الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: $requestName",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// ✅ لوق داخلي
|
||||
error_log("🔐 Signature for $requestName: $xSignature");
|
||||
error_log("📤 Sent JSON: $bodyJson");
|
||||
|
||||
curl_close($ch);
|
||||
return json_decode($response, true);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
// --- 2. استقبال البيانات من الطلب ---
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$driver_name = filterRequest("driver_name");
|
||||
$amount = filterRequest("amount");
|
||||
$wallet_type = filterRequest("wallet_type");
|
||||
$wallet_number = filterRequest("wallet_number");
|
||||
|
||||
// التحقق من أن البيانات الأساسية موجودة
|
||||
if (empty($driver_id) || empty($driver_name) || empty($amount) || empty($wallet_type) || empty($wallet_number)) {
|
||||
printFailure('Missing required fields.');
|
||||
exit();
|
||||
}
|
||||
|
||||
// --- 3. إدراج الطلب في قاعدة البيانات ---
|
||||
try {
|
||||
$sql = "INSERT INTO driver_withdrawal_requests (driver_id, driver_name, amount, wallet_type, wallet_number) VALUES (?, ?, ?, ?, ?)";
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// تنفيذ الاستعلام مع تمرير البيانات
|
||||
$success = $stmt->execute([
|
||||
$driver_id,
|
||||
$driver_name,
|
||||
$amount,
|
||||
$wallet_type,
|
||||
$wallet_number
|
||||
]);
|
||||
|
||||
if ($success) {
|
||||
// --- 4. الحصول على رقم الطلب وإرسال إشعار واتساب ---
|
||||
$transaction_id = $con->lastInsertId(); // الحصول على رقم التعريف الخاص بالطلب الجديد
|
||||
sendWhatsAppNotification($transaction_id, $driver_name, $amount, $wallet_type, $wallet_number);
|
||||
|
||||
// إرسال استجابة نجاح إلى التطبيق
|
||||
printSuccess("Withdrawal request saved and notification sent.");
|
||||
|
||||
} else {
|
||||
printFailure('Failed to save withdrawal request.');
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// التعامل مع أخطاء قاعدة البيانات
|
||||
error_log("Database Error in request_withdrawal.php: " . $e->getMessage());
|
||||
printFailure('A database error occurred.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* دالة لإرسال إشعار إلى خدمة العملاء عبر RaseelPlus API
|
||||
*/
|
||||
function sendWhatsAppNotification($transaction_id, $driver_name, $amount, $wallet_type, $wallet_number) {
|
||||
|
||||
// استخدام متغيرات البيئة (Environment Variables) هو الطريقة الأكثر أماناً لإدارة المعلومات الحساسة
|
||||
// بدلاً من كتابتها مباشرة في الكود.
|
||||
$customer_service_number = getenv('CUSTOMER_SERVICE_WHATSAPP');
|
||||
// $customer_service_number = "9639XXXXXXXX"; // كرقم احتياطي مؤقت
|
||||
|
||||
// نص الرسالة مع إضافة رقم الطلب
|
||||
$messageBody = "طلب سحب جديد (رقم الطلب: #$transaction_id):\n\n" .
|
||||
"اسم السائق: " . $driver_name . "\n" .
|
||||
"المبلغ: " . $amount . " ل.س\n" .
|
||||
"نوع المحفظة: " . $wallet_type . "\n" .
|
||||
"رقم المحفظة: " . $wallet_number;
|
||||
|
||||
// بيانات الطلب (Payload) للـ API
|
||||
$payload = [
|
||||
"number" => $customer_service_number,
|
||||
"type" => "text",
|
||||
"message" => $messageBody,
|
||||
"instance_id" => getenv('instance_idWhatsApp');
|
||||
"access_token" => getenv('access_tokenWhatsApp');
|
||||
];
|
||||
|
||||
// استدعاء الـ API
|
||||
// ملاحظة: لا نتحقق من استجابة الـ API هنا لأن العملية الرئيسية (حفظ الطلب) قد نجحت بالفعل.
|
||||
// يمكن إضافة تسجيل للأخطاء إذا لزم الأمر.
|
||||
callAPI_Withdrawal("POST", "https://raseelplus.com/api/send", json_encode($payload));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* دالة لإجراء استدعاءات API باستخدام cURL
|
||||
*/
|
||||
function callAPI_Withdrawal($method, $url, $data) {
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Accept: application/json"
|
||||
],
|
||||
]);
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
// تسجيل الخطأ في سجلات الخادم للمراجعة لاحقًا
|
||||
error_log("[callAPI_Withdrawal] cURL Error #: " . $err);
|
||||
return null;
|
||||
} else {
|
||||
return json_decode($response);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // الـ OTP
|
||||
|
||||
error_log("MTN Confirm: Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
error_log("MTN Confirm: Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تشفير الكود
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
$body = [
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => intval($operationNumber),
|
||||
'Code' => $codeB64
|
||||
];
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
|
||||
error_log("MTN Confirm: Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// توليد التوقيع
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult) {
|
||||
error_log("MTN Confirm: Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
error_log("MTN Confirm: Generated signature");
|
||||
|
||||
// إرسال الطلب
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("MTN Confirm: HTTP $httpCode - Response: $response");
|
||||
if ($curlError) {
|
||||
error_log("MTN Confirm: cURL error - $curlError");
|
||||
}
|
||||
|
||||
// تحديث قاعدة البيانات في حال نجاح
|
||||
if ($httpCode === 200) {
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
error_log("MTN Confirm: Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLogSyria WHERE order_ref = :order_ref LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $invoice]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($payment) {
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'mtn';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
|
||||
printSuccess('MTN Confirm');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("MTN Confirm: DB update error - " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
error_log("MTN Confirm: Payment failed with HTTP code $httpCode");
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 200000) return 205000;
|
||||
if ($amount == 400000) return 425000;
|
||||
if ($amount == 1000000) return 1040000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// wallet/finalize_wallet_payment.php
|
||||
include_once "../../../jwtconnect.php";
|
||||
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $method): ?string {
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
|
||||
function finalizeWalletPayment($con) {
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
logError("FINALIZE", "Missing orderRef");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. تحقق من الدفع
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("FINALIZE", "Payment not found or not completed", ['orderRef' => $orderRef]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
// حساب المكافأة
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// إنشاء التوكنات
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate driver token');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate siro token');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID');
|
||||
|
||||
// driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonusAmount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed');
|
||||
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenDriver]);
|
||||
|
||||
// siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenSiro]);
|
||||
|
||||
logError("FINALIZE", "Wallets updated successfully", ['orderRef' => $orderRef]);
|
||||
printSuccess("FINALIZE", "Wallets updated successfully");
|
||||
} catch (Throwable $e) {
|
||||
logError("FINALIZE", "Exception during finalization: " . $e->getMessage(), ['orderRef' => $orderRef]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// File: generate_keys.php
|
||||
// الوظيفة: إنشاء زوج المفاتيح (العام والخاص) لمرة واحدة فقط
|
||||
|
||||
// إعدادات لتوليد المفتاح
|
||||
$config = [
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 1024,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
// إنشاء زوج المفاتيح
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if (!$res) {
|
||||
die('Failed to generate new private key. Error: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// استخراج المفتاح الخاص
|
||||
openssl_pkey_export($res, $private_key);
|
||||
|
||||
// استخراج المفتاح العام
|
||||
$public_key_details = openssl_pkey_get_details($res);
|
||||
$public_key = $public_key_details["key"];
|
||||
|
||||
// حفظ المفاتيح في ملفات
|
||||
file_put_contents('private_key.pem', $private_key);
|
||||
file_put_contents('public_key.pem', $public_key);
|
||||
|
||||
echo "<h1>Keys Generated Successfully!</h1>";
|
||||
echo "<h2>Private Key (saved to private_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($private_key) . "</pre>";
|
||||
echo "<h2>Public Key (saved to public_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($public_key) . "</pre>";
|
||||
|
||||
// --- تحضير المفتاح العام لعملية التفعيل ---
|
||||
// إزالة الهيدر والفوتر والأسطر الجديدة كما هو مطلوب
|
||||
$formatted_public_key = str_replace("-----BEGIN PUBLIC KEY-----", "", $public_key);
|
||||
$formatted_public_key = str_replace("-----END PUBLIC KEY-----", "", $formatted_public_key);
|
||||
$formatted_public_key = preg_replace("/\s+/", "", $formatted_public_key);
|
||||
|
||||
|
||||
echo "<h2>Formatted Public Key (for Terminal Activation):</h2>";
|
||||
echo "<p><strong>انسخ هذا المفتاح لاستخدامه في خطوة تفعيل الجهاز (activate_terminal.php)</strong></p>";
|
||||
echo "<textarea rows='5' cols='80' readonly>" . htmlspecialchars($formatted_public_key) . "</textarea>";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/initiate_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
|
||||
$invoice = filterRequest('invoice'); // رقم الفاتورة
|
||||
$phone = filterRequest('phone'); // رقم الزبون
|
||||
$guid = uniqid('mtn_');
|
||||
|
||||
if (!$invoice || !$phone) {
|
||||
printFailure("Missing invoice or phone.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = json_encode([
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid
|
||||
], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$hash = hash('sha256', $body, true);
|
||||
$pkey = openssl_get_privatekey($privateKeyPem);
|
||||
if (!$pkey) {
|
||||
error_log("[MTN Initiate] Failed to load private key");
|
||||
printFailure("Payment configuration error");
|
||||
exit;
|
||||
}
|
||||
openssl_sign($hash, $sig, $pkey, OPENSSL_ALGO_SHA256);
|
||||
openssl_free_key($pkey);
|
||||
$xSignature = base64_encode($sig);
|
||||
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/initiate");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/initiate",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// سجل المحاولة مع Guid
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `mtn_payments`
|
||||
SET guid = :guid, status = 3, updated_at = NOW()
|
||||
WHERE invoice = :inv"
|
||||
);
|
||||
$stmt->execute([':guid'=>$guid, ':inv'=>$invoice]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// بيانات التفعيل
|
||||
$terminalId = "9001000000060863";
|
||||
$activationCode = "26164711";
|
||||
$serialNumber = "INTALEQ-001"; // يمكنك تغييره
|
||||
|
||||
// المفتاح العام على سطر واحد — بدون BEGIN/END وبدون أسطر جديدة
|
||||
//$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNxFbepx2OrpyrNG4+/aAaH3Rjc8dGw6B6vMAfsZzzm4wzoSkrtsr6jfuaMTZRLwxS5h8k1ztLG1HrOmL/NDsiE/7yxaKLAIZyWB/rR9byvPeOCC8QnCd/08kmxNl/l7Akn6qlPwsVpKUUNsr0SkU9lShMAw4OBgQq399jsbkFSwIDAQAB";
|
||||
$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOhVAdUyxFpVNSyjRndMWEPAN9vJEetMzLbjF9DTn2lPVuRj/Mkwq9wCNhy+tdeX2lIn4K3EkONBvYJubBhxnYOoQuMchPW5vG7VnmpLjZ7TkpM2n9fcMu8u1GkLatLblDI4LTfvn3851+nhpnYlUVkjw5GAhH4XnEpveIjqDhzQIDAQAB";
|
||||
// جسم الطلب
|
||||
$body = [
|
||||
"Key" => $publicKey,
|
||||
"Secret" => $activationCode,
|
||||
"Serial" => $serialNumber
|
||||
];
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES);
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)),'"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// 1. توليد هاش SHA256 للـ JSON
|
||||
//$bodyHash = hash('sha256', $bodyJson, true);
|
||||
|
||||
// 2. تحميل المفتاح الخاص للتوقيع
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem")); // تأكد من وجود هذا الملف بجانب السكربت
|
||||
|
||||
// 3. توقيع الهاش
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
|
||||
// 4. تحويل التوقيع إلى Base64
|
||||
$xSignature = base64_encode($signature);
|
||||
|
||||
// 5. إرسال الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: pos_web/pos/activate",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init("https://cashmobile.mtnsyr.com:9000");
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// ✅ النتيجة
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
"httpCode" => $httpCode,
|
||||
"response" => json_decode($response, true),
|
||||
"sentBody" => $body,
|
||||
]);
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
function mlog(string $msg) { error_log($msg); } // timestamp يضاف تلقائياً من PHP
|
||||
|
||||
// قاعدة URL للتطبيق (لمعالجة BASE_URL غير المعروفة)
|
||||
if (!defined('BASE_URL')) {
|
||||
$APP_BASE_URL = rtrim(getenv('APP_BASE_URL') ?: '', '/');
|
||||
if ($APP_BASE_URL === '') {
|
||||
$scheme = isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'https';
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
|
||||
define('BASE_URL', $scheme . '://' . $host);
|
||||
} else {
|
||||
define('BASE_URL', $APP_BASE_URL);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // الـ OTP
|
||||
$lang = filterRequest("lang");
|
||||
|
||||
mlog("MTN Confirm: Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
mlog("MTN Confirm: Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تشفير الكود (SHA256 ثم Base64)
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
// جسم الطلب نحو MTN
|
||||
$body = [
|
||||
'Invoice' => (int)$invoice,
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => (int)$operationNumber,
|
||||
'Code' => $codeB64,
|
||||
'Accept-Language' => $lang
|
||||
];
|
||||
$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE);
|
||||
mlog("MTN Confirm: Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// توقيع الجسم
|
||||
$sig = null;
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult || !$sig) {
|
||||
mlog("MTN Confirm: Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
mlog("MTN Confirm: Generated signature");
|
||||
|
||||
// إرسال الطلب إلى MTN
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
],
|
||||
CURLOPT_TIMEOUT => 25,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
mlog("MTN Confirm: HTTP {$httpCode} - Response: " . ($response ?? ''));
|
||||
if ($curlError) {
|
||||
mlog("MTN Confirm: cURL error - {$curlError}");
|
||||
}
|
||||
|
||||
// فك JSON لرد MTN (حتى لو خطأ) لعرض سبب واضح
|
||||
$mtn = json_decode($response ?: '{}', true);
|
||||
if (!is_array($mtn)) {
|
||||
$mtn = [];
|
||||
}
|
||||
|
||||
// 🧠 سياسة القرار:
|
||||
// - إذا HTTP≠200 → فشل شبكة/بوابة
|
||||
// - إذا HTTP=200 لكن Errno≠0 → خطأ من MTN (مثل Incorrect sms code)
|
||||
// - فقط إذا HTTP=200 && Errno=0 → نجاح، نحدّث DB ونضيف للمحافظ
|
||||
if ($httpCode !== 200) {
|
||||
// لا تحدّث DB
|
||||
printFailure([
|
||||
'message' => 'MTN confirm HTTP failure',
|
||||
'http' => $httpCode,
|
||||
'mtn' => $mtn
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// HTTP 200 — افحص Errno
|
||||
$errno = isset($mtn['Errno']) ? (int)$mtn['Errno'] : null;
|
||||
if ($errno !== 0) {
|
||||
// لا تحدّث DB في هذه الحالة
|
||||
$errText = isset($mtn['Error']) ? $mtn['Error'] : 'Unknown MTN error';
|
||||
// اطبع لوج مشابه للمثال المطلوب
|
||||
// مثال: {"Errno":662,"Error":"Incorrect sms code","Abuse":2,"Transaction":""}
|
||||
mlog("MTN Confirm: Business failure from MTN - Errno={$errno}, Error=" . json_encode($mtn, JSON_UNESCAPED_UNICODE));
|
||||
printFailure([
|
||||
'message' => $errText,
|
||||
'errno' => $errno,
|
||||
'mtn' => $mtn
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ نجاح كامل من MTN — تحديث DB ثم المحافظ
|
||||
try {
|
||||
global $con;
|
||||
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
mlog("MTN Confirm: Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLogSyria WHERE order_ref = :order_ref LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $invoice]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
mlog("MTN Confirm: Payment row not found after update");
|
||||
printFailure("Payment row not found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'mtn';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
|
||||
// رجّع رد موحّد + ضمّن رد MTN
|
||||
printSuccess([
|
||||
'message' => 'MTN Confirm',
|
||||
'data' => [
|
||||
'invoice' => $invoice,
|
||||
'finalAmount' => $finalAmount,
|
||||
'wallet' => $walletResult,
|
||||
'siroWallet' => $siroWalletResult,
|
||||
'mtn' => $mtn
|
||||
]
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
mlog("MTN Confirm: DB update error - " . $e->getMessage());
|
||||
printFailure("DB error");
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
mlog("MTN Confirm: Exception - " . $e->getMessage());
|
||||
printFailure("Server error");
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* نفس دوال المساعدة، لكن باستعمال BASE_URL المؤمّنة أعلاه
|
||||
*/
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 20000) return 20500;
|
||||
if ($amount == 40000) return 42500;
|
||||
if ($amount == 100000) return 104000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = rtrim(BASE_URL, '/') . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = rtrim(BASE_URL, '/') . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = rtrim(BASE_URL, '/') . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
date_default_timezone_set("Asia/Damascus");
|
||||
|
||||
// ========== إعدادات MTN ==========
|
||||
$terminalId = "9001000000060863";
|
||||
$currencyCode = 760;
|
||||
$sessionNumber = 0;
|
||||
$ttl = 15;
|
||||
|
||||
// ====== استقبال البيانات من فلاتر ======
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$phone = filterRequest("phone");
|
||||
$lang = filterRequest("lang");
|
||||
|
||||
// ✅ Log مبدئي
|
||||
error_log("🚦 START | passengerId: $passengerId | phone: $phone | amount: $amount");
|
||||
|
||||
// تحقق من المدخلات
|
||||
if (empty($amount) || empty($passengerId) || empty($phone) || $amount <= 0) {
|
||||
error_log("❌ Invalid input: amount=$amount, passengerId=$passengerId, phone=$phone");
|
||||
printFailure("بيانات الدفع غير كاملة أو غير صالحة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== توليد invoiceNumber و GUID ======
|
||||
$invoiceNumber = mt_rand(10000000000, 99999999999);
|
||||
//$invoiceNumber = "MTN_" . $passengerId . "_" . time();
|
||||
$guid = uniqid("mtn_");
|
||||
error_log("🧾 Generated Invoice: $invoiceNumber");
|
||||
error_log("🧭 Generated GUID: $guid");
|
||||
|
||||
// ====== 1. إنشاء الفاتورة ======
|
||||
$createInvoiceBody = [
|
||||
"Amount" => intval($amount * 100),
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Session" => $sessionNumber,
|
||||
"TTL" => $ttl
|
||||
];
|
||||
error_log("📦 Create Invoice Body: " . json_encode($createInvoiceBody, JSON_UNESCAPED_UNICODE));
|
||||
$invoiceResponse = sendMtnApiRequest("pos_web/invoice/create", $terminalId, $createInvoiceBody);
|
||||
error_log("📥 Create Invoice Response: " . json_encode($invoiceResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$invoiceResponse || isset($invoiceResponse['Errno']) && $invoiceResponse['Errno'] != 0) {
|
||||
error_log("❌ Failed to create invoice. Error: " . json_encode($invoiceResponse));
|
||||
printFailure("فشل إنشاء الفاتورة عبر MTN.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 2. بدء الدفع ======
|
||||
$initiateBody = [
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Phone" => $phone,
|
||||
"Guid" => $guid
|
||||
];
|
||||
error_log("📤 body initiateBody: $initiateBody");
|
||||
error_log("📦 Initiate Payment Body: " . json_encode($initiateBody, JSON_UNESCAPED_UNICODE));
|
||||
$initiateResponse = sendMtnApiRequest("pos_web/payment_phone/initiate", $terminalId, $initiateBody);
|
||||
error_log("📥 Initiate Response: " . json_encode($initiateResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$initiateResponse || !isset($initiateResponse['OperationNumber'])) {
|
||||
error_log("❌ Failed to initiate payment.");
|
||||
printFailure($initiateResponse);
|
||||
exit;
|
||||
}
|
||||
|
||||
$operationNumber = $initiateResponse['OperationNumber'];
|
||||
|
||||
// ====== 3. تسجيل العملية ======
|
||||
try {
|
||||
$stmt = $con->prepare("INSERT INTO `paymentsLogSyria`
|
||||
(`user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (?, ?, 2, ?, 'mtn', NOW())");
|
||||
$stmt->execute([$passengerId, $amount, $invoiceNumber]);
|
||||
error_log("✅ DB Log Inserted.");
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ DB ERROR: " . $e->getMessage());
|
||||
printFailure("فشل في تسجيل العملية.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 4. نجاح
|
||||
error_log("✅ Payment initiation successful.");
|
||||
printSuccess([
|
||||
"invoiceNumber" => $invoiceNumber,
|
||||
"operationNumber" => $operationNumber,
|
||||
"guid" => $guid
|
||||
]);
|
||||
|
||||
|
||||
// ====== دالة إرسال الطلب =====================
|
||||
function sendMtnApiRequest($requestName, $terminalId, $body)
|
||||
{
|
||||
$apiUrl = "https://cashmobile.mtnsyr.com:9000";
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
|
||||
// ✅ تحويل الـ body إلى JSON بدون فراغات أو أسطر
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// ✅ توليد التوقيع
|
||||
// $bodyHash = hash('sha256', $bodyJson, true);
|
||||
error_log("📤 body before JSON: $bodyJson");
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
$xSignature = base64_encode($signature);
|
||||
error_log("📤 body xSignature: $xSignature");
|
||||
// ✅ رؤوس الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: $lang",
|
||||
"Request-Name: $requestName",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// ✅ لوق داخلي
|
||||
error_log("🔐 Signature for $requestName: $xSignature");
|
||||
error_log("📤 Sent JSON: $bodyJson");
|
||||
|
||||
curl_close($ch);
|
||||
return json_decode($response, true);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// --- check_status.php ---
|
||||
include "../../connect.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$invoiceNumber = filterRequest("invoice_number");
|
||||
|
||||
if (empty($invoiceNumber)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $con->prepare("SELECT status FROM mtn_invoices WHERE invoice_number = :invoice_number LIMIT 1");
|
||||
$stmt->execute([':invoice_number' => $invoiceNumber]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($invoice) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"invoice_status" => $invoice['status']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice not found."]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in check_status.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error."]);
|
||||
}
|
||||
?>
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// --- create_mtn_invoice.php ---
|
||||
// إذا كانت هناك فاتورة Pending لليوم نفسه لنفس المستخدم: نُحدّثها
|
||||
// غير ذلك: نُنشئ فاتورة جديدة
|
||||
|
||||
include "../../connect.php";
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$userId = filterRequest("user_id"); // driverID أو passengerID
|
||||
$userType = filterRequest("user_type"); // 'driver' أو 'passenger'
|
||||
$amount = filterRequest("amount");
|
||||
$mtnPhone = filterRequest("mtn_phone");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
if (empty($userId) || empty($userType) || !is_numeric($amount) || $amount <= 0 || empty($mtnPhone)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invalid input provided."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// نضمن اتساق العمليات
|
||||
$con->beginTransaction();
|
||||
|
||||
// ابحث عن فاتورة PENDING لنفس المستخدم، منشأة "اليوم"
|
||||
// ملاحظة: يتطلب وجود عمود created_at (TIMESTAMP) في الجدول
|
||||
$sel = $con->prepare("
|
||||
SELECT id, invoice_number
|
||||
FROM mtn_invoices
|
||||
WHERE user_id = :uid
|
||||
AND user_type = :utype
|
||||
AND status = 'pending'
|
||||
AND DATE(created_at) = CURRENT_DATE
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$sel->execute([
|
||||
':uid' => $userId,
|
||||
':utype' => $userType,
|
||||
]);
|
||||
$existing = $sel->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existing) {
|
||||
// يوجد سجل لليوم: نحدّث على نفس الفاتورة
|
||||
$upd = $con->prepare("
|
||||
UPDATE mtn_invoices
|
||||
SET amount = :amount,
|
||||
phone = :phone,
|
||||
mtn_phone = :mtn_phone,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$upd->execute([
|
||||
':amount' => $amount,
|
||||
':phone' => $phone ?: null,
|
||||
':mtn_phone'=> $mtnPhone,
|
||||
':id' => $existing['id'],
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Invoice updated (pending, same day).",
|
||||
"invoice_number" => $existing['invoice_number'],
|
||||
"mode" => "updated"
|
||||
]);
|
||||
} else {
|
||||
// لا يوجد سجل لليوم: ننشئ فاتورة جديدة
|
||||
$invoiceNumber = "MTN-" . time() . mt_rand(100, 999);
|
||||
|
||||
$ins = $con->prepare("
|
||||
INSERT INTO mtn_invoices
|
||||
(invoice_number, user_id, user_type, phone, amount, mtn_phone, status, created_at, updated_at)
|
||||
VALUES
|
||||
(:invoice_number, :user_id, :user_type, :phone, :amount, :mtn_phone, 'pending', NOW(), NOW())
|
||||
");
|
||||
$ins->execute([
|
||||
':invoice_number' => $invoiceNumber,
|
||||
':user_id' => $userId,
|
||||
':user_type' => $userType,
|
||||
':phone' => $phone ?: null,
|
||||
':amount' => $amount,
|
||||
':mtn_phone' => $mtnPhone
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
// تحديد الرقم أو الاسم المستعار للدفع
|
||||
$mtnPaymentNumber = $_ENV['MTN_PAYMENT_NUMBER'] ?? getenv('MTN_PAYMENT_NUMBER') ?: '0930000000';
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Invoice created successfully.",
|
||||
"invoice_number" => $invoiceNumber,
|
||||
"mtn_payment_number" => $mtnPaymentNumber,
|
||||
"mode" => "inserted"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($con && $con->inTransaction()) { $con->rollBack(); }
|
||||
error_log("Error in create_mtn_invoice.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "failure", "message" => "An internal server error occurred."]);
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// --- finalize_payment.php ---
|
||||
// يحتوي على الدوال المنطقية لإضافة الرصيد للمستخدمين بعد تأكيد الدفع
|
||||
|
||||
// ملاحظة: هذا الملف لا يتم استدعاؤه مباشرة، بل يتم تضمينه في mtn_webhook_handler.php
|
||||
|
||||
/**
|
||||
* دالة مركزية لإتمام الدفع بعد التحقق منه
|
||||
* @param PDO $con اتصال قاعدة البيانات
|
||||
* @param int $invoiceId معرّف الفاتورة في جدول mtn_invoices
|
||||
* @return array نتيجة العملية
|
||||
*/
|
||||
function finalizeMtnPayment(PDO $con, int $invoiceId): array
|
||||
{
|
||||
try {
|
||||
// جلب تفاصيل الفاتورة
|
||||
$stmt = $con->prepare("SELECT * FROM `mtn_invoices` WHERE id = :id AND status = 'completed' LIMIT 1");
|
||||
$stmt->execute([':id' => $invoiceId]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$invoice) {
|
||||
return ['success' => false, 'message' => 'Invoice not found or not completed.'];
|
||||
}
|
||||
|
||||
$userType = $invoice['user_type'];
|
||||
$userId = $invoice['user_id'];
|
||||
$amount = (float) $invoice['amount'];
|
||||
$paymentMethod = 'mtn_cash'; // تحديد طريقة الدفع
|
||||
|
||||
// تحديد ما إذا كان المستخدم سائقاً أم راكباً
|
||||
if ($userType === 'driver') {
|
||||
return finalizeForDriver($con, $userId, $amount, $paymentMethod);
|
||||
} elseif ($userType === 'passenger') {
|
||||
return finalizeForPassenger($con, $userId, $amount, $paymentMethod);
|
||||
} else {
|
||||
return ['success' => false, 'message' => 'Unknown user type.'];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Finalization Exception: " . $e->getMessage());
|
||||
return ['success' => false, 'message' => 'Finalization failed'];
|
||||
}
|
||||
}
|
||||
|
||||
// --- دوال مساعدة خاصة بالسائق ---
|
||||
function finalizeForDriver(PDO $con, int $driverId, float $amount, string $paymentMethod): array
|
||||
{
|
||||
// حساب قيمة البونص كما في الكود الأصلي
|
||||
$bonusAmount = match ((int)$amount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $amount,
|
||||
};
|
||||
|
||||
// إنشاء سجل دفع جديد والحصول على ID
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID for driver.');
|
||||
|
||||
// إضافة الرصيد لمحفظة السائق
|
||||
$stmtDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$stmtDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod) VALUES (:driverId, 'driver', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':driverId' => $driverId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet failed.');
|
||||
|
||||
return ['success' => true, 'message' => 'Driver wallets updated.'];
|
||||
}
|
||||
|
||||
function generatePaymentID(PDO $con, string $driverId, float $amount, string $method): ?string {
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
|
||||
|
||||
// --- دوال مساعدة خاصة بالراكب ---
|
||||
function finalizeForPassenger(PDO $con, string $passengerId, float $amount, string $paymentMethod): array
|
||||
{
|
||||
// حساب البونص للراكب
|
||||
$finalAmount = calculatePassengerBonus($amount);
|
||||
|
||||
// إضافة الرصيد لمحفظة الراكب
|
||||
$stmtPassenger = $con->prepare("INSERT INTO passengerWallet (passenger_id, balance) VALUES (:id, :amount) ON DUPLICATE KEY UPDATE balance = balance + :amount");
|
||||
$stmtPassenger->execute([':id' => $passengerId, ':amount' => $finalAmount]);
|
||||
if ($stmtPassenger->rowCount() === 0) throw new Exception('Update passengerWallet failed.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (passengerId, driverId, amount, paymentMethod) VALUES (:passengerId, 'passenger', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':passengerId' => $passengerId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet for passenger failed.');
|
||||
|
||||
return ['success' => true, 'message' => 'Passenger wallets updated.'];
|
||||
}
|
||||
|
||||
function calculatePassengerBonus(float $amount): float {
|
||||
if ($amount == 20000) return 20500;
|
||||
if ($amount == 40000) return 42500;
|
||||
if ($amount == 100000) return 104000;
|
||||
return $amount;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
// --- mtn_webhook_handler.php ---
|
||||
// هذا هو الـ Webhook الرئيسي الذي يستقبل إشعار تأكيد الدفع من MTN
|
||||
|
||||
include "../../jwtconnect.php";
|
||||
include "./finalize_payment.php"; // تضمين ملف إتمام العملية
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// **مهم جداً: التحقق من مصدر الطلب**
|
||||
// يجب التحقق من أن هذا الطلب قادم فعلاً من MTN وليس من أي طرف آخر
|
||||
// المثال التالي يستخدم مفتاح سري مشترك (Shared Secret)
|
||||
$expectedToken = trim(file_get_contents('/home/intaleq-wallet/.mtnKey')); // يجب استبداله بتوكن حقيقي
|
||||
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
|
||||
if (!hash_equals($expectedToken, $receivedToken)) {
|
||||
http_response_code(401); // Unauthorized
|
||||
echo json_encode(["status" => "error", "message" => "Authentication failed."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// قراءة البيانات القادمة من MTN (عادة تكون بصيغة JSON في الـ body)
|
||||
$json_data = file_get_contents('php://input');
|
||||
$data = json_decode($json_data, true);
|
||||
|
||||
$invoiceNumber = $data['invoice_number'] ?? null;
|
||||
$transactionId = $data['transaction_id'] ?? null;
|
||||
$paymentStatus = $data['status'] ?? null;
|
||||
|
||||
if (empty($invoiceNumber) || empty($transactionId) || $paymentStatus !== 'success') {
|
||||
http_response_code(400); // Bad Request
|
||||
echo json_encode(["status" => "error", "message" => "Missing or invalid payment data."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// 1. البحث عن الفاتورة وتحديث حالتها
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `mtn_invoices`
|
||||
SET `status` = 'completed', `mtn_transaction_id` = :transaction_id
|
||||
WHERE `invoice_number` = :invoice_number AND `status` = 'pending'"
|
||||
);
|
||||
$stmt->execute([
|
||||
':transaction_id' => $transactionId,
|
||||
':invoice_number' => $invoiceNumber
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// تم تحديث الفاتورة بنجاح، الآن نقوم بإتمام العملية
|
||||
$invoiceId = $con->lastInsertId(); // ملاحظة: هذا قد لا يعمل دائماً مع UPDATE، الأفضل جلب الـ ID
|
||||
|
||||
// جلب ID الفاتورة بعد التأكد من وجودها
|
||||
$idStmt = $con->prepare("SELECT id FROM `mtn_invoices` WHERE `invoice_number` = :invoice_number");
|
||||
$idStmt->execute([':invoice_number' => $invoiceNumber]);
|
||||
$invoiceRecord = $idStmt->fetch();
|
||||
$invoiceId = $invoiceRecord['id'];
|
||||
|
||||
$finalizationResult = finalizeMtnPayment($con, $invoiceId);
|
||||
|
||||
if ($finalizationResult['success']) {
|
||||
$con->commit();
|
||||
echo json_encode(["status" => "success", "message" => "Transaction finalized."]);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
// يجب هنا التعامل مع الحالة التي فشل فيها الإيداع رغم نجاح الدفع
|
||||
error_log("CRITICAL: Payment received for invoice {$invoiceNumber} but finalization failed.");
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "error", "message" => "Finalization failed."]);
|
||||
}
|
||||
} else {
|
||||
// لم يتم العثور على فاتورة معلقة بهذا الرقم (ربما تمت معالجتها سابقاً)
|
||||
$con->rollBack();
|
||||
http_response_code(404);
|
||||
echo json_encode(["status" => "error", "message" => "Invoice not found or already processed."]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log("Error in mtn_webhook_handler.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "error", "message" => "An internal server error occurred."]);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// --- query_mtn_invoice.php ---
|
||||
// هذا السكربت هو نقطة الـ Webhook التي سيستدعيها نظام MTN
|
||||
// للاستعلام عن وجود فاتورة دفع معلقة للمستخدم قبل أن يدفع
|
||||
|
||||
include "../../jwtconnect.php"; // تأكد من أن هذا المسار صحيح
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// يمكن إضافة طبقة حماية هنا للتحقق من أن الطلب قادم من سيرفرات MTN
|
||||
// مثلاً عبر التحقق من IP أو من وجود Secret Key في الـ Headers
|
||||
// --- آلية الحماية ---
|
||||
$shared_secret_key = trim(file_get_contents('/home/intaleq-wallet/.mtnKey'));
|
||||
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
|
||||
if ($receivedToken !== $shared_secret_key) {
|
||||
http_response_code(401); // Unauthorized
|
||||
echo json_encode(['status' => 'error', 'message' => 'Authentication failed. Invalid or missing token.']);
|
||||
exit;
|
||||
}
|
||||
try {
|
||||
// يفترض أن MTN سترسل رقم هاتف المستخدم للاستعلام عنه
|
||||
//$mtnPhone = filterRequest("mtn_phone");
|
||||
$mtnPhone = $_GET['phone_number'] ?? null;
|
||||
|
||||
if (empty($mtnPhone)) {
|
||||
echo json_encode(["status" => "error", "message" => "Phone number is required."]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// البحث عن فاتورة معلقة لهذا الرقم
|
||||
$stmt = $con->prepare(
|
||||
"SELECT invoice_number, amount, user_id, user_type
|
||||
FROM `mtn_invoices`
|
||||
WHERE `mtn_phone` = :mtn_phone AND `status` = 'pending'
|
||||
ORDER BY `created_at` DESC LIMIT 1"
|
||||
);
|
||||
$stmt->execute([':mtn_phone' => $mtnPhone]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($invoice) {
|
||||
// تم العثور على فاتورة، يتم إرجاع تفاصيلها لنظام MTN
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"statusInvoice" => "pending",
|
||||
"invoice_number" => $invoice['invoice_number'],
|
||||
"amount" => (float) $invoice['amount'],
|
||||
"description" => "شحن نقاط في تطبيق انطلق", // وصف يظهر للمستخدم في تطبيق MTN
|
||||
"biller_name" => "Intaleq App"
|
||||
|
||||
]);
|
||||
} else {
|
||||
// لا توجد فاتورة معلقة
|
||||
echo json_encode(["status" => "error", "message" => "No pending invoice found for this number."]);
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in query_mtn_invoice.php: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Internal server error."]);
|
||||
http_response_code(500);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// --- verify_payment_ai.php ---
|
||||
include "../../connect.php";
|
||||
include "./finalize_payment.php";
|
||||
include "../GeminiAi.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$json_data = file_get_contents('php://input');
|
||||
$data = json_decode($json_data, true) ?: $_POST;
|
||||
|
||||
$invoiceNumber = $data['invoice_number'] ?? '';
|
||||
$proofText = $data['proof_text'] ?? '';
|
||||
$proofImageBase64 = $data['proof_image_base64'] ?? '';
|
||||
|
||||
if (empty($invoiceNumber)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($proofText) && empty($proofImageBase64)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Proof text or image is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $con->prepare("SELECT id, amount FROM mtn_invoices WHERE invoice_number = :inv AND status = 'pending'");
|
||||
$stmt->execute([':inv' => $invoiceNumber]);
|
||||
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$invoice) {
|
||||
echo json_encode(["status" => "failure", "message" => "Invoice not found or already processed."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$amount = $invoice['amount'];
|
||||
|
||||
$geminiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?: '';
|
||||
|
||||
if (empty($geminiKey)) {
|
||||
echo json_encode(["status" => "error", "message" => "Gemini API key not configured."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment($invoiceNumber, $amount, "MTN", $proofText, $proofImageBase64);
|
||||
|
||||
if (isset($aiResult['verified']) && $aiResult['verified'] === true) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("UPDATE mtn_invoices SET status = 'completed', updated_at = NOW() WHERE id = :id AND status = 'pending'");
|
||||
$upd->execute([':id' => $invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
$finalizationResult = finalizeMtnPayment($con, $invoice['id']);
|
||||
if ($finalizationResult['success']) {
|
||||
$con->commit();
|
||||
echo json_encode(["status" => "success", "message" => "Payment verified and finalized."]);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode(["status" => "error", "message" => "Verification succeeded but finalization failed."]);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode(["status" => "error", "message" => "Invoice already processed."]);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? "AI rejected the proof.";
|
||||
echo json_encode(["status" => "failure", "message" => "Verification failed: $reason"]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in MTN verify: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error."]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Migration: Add phone column to all invoice tables
|
||||
-- Allows direct phone lookup instead of S2S resolve_user
|
||||
-- Run: mysql -u root WalletIntaleqDB < migration_add_phone.sql
|
||||
|
||||
ALTER TABLE invoices_shamcash
|
||||
ADD COLUMN phone VARCHAR(20) AFTER driverID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_shamcash_passenger
|
||||
ADD COLUMN phone VARCHAR(20) AFTER passengerID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE cliq_invoices
|
||||
ADD COLUMN phone VARCHAR(20) AFTER user_type,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_sms
|
||||
ADD COLUMN phone VARCHAR(20) AFTER driverID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_sms_passenger
|
||||
ADD COLUMN phone VARCHAR(20) AFTER passengerID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE mtn_invoices
|
||||
ADD COLUMN phone VARCHAR(20) AFTER user_type,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Payment Verification Endpoint
|
||||
*
|
||||
* Simplified: uses phone directly to find pending invoice (no S2S resolve_user).
|
||||
* Added Cliq AI verification with receipt image.
|
||||
*
|
||||
* ===============================
|
||||
* INPUT (JSON body)
|
||||
* ===============================
|
||||
* phone (required) — User's phone number
|
||||
* payment_method (req) — shamcash / cliq / sms / mtn
|
||||
* receipt_image (opt) — Receipt screenshot for AI verification
|
||||
* image_mime_type (opt) — Default: image/jpeg
|
||||
*
|
||||
* ===============================
|
||||
* FLOW
|
||||
* ===============================
|
||||
* 1. Auth via jwtconnect.php (X-API-Key → NABEH_API_KEY)
|
||||
* 2. Find latest pending invoice by phone + payment_method
|
||||
* 3. If shamcash/cliq + receipt_image → Gemini AI verification
|
||||
* AI confirms → update status → finalize deposit → return success
|
||||
* 4. Otherwise return invoice status
|
||||
*
|
||||
* Auth: X-API-Key header → NABEH_API_KEY (via jwtconnect.php Path 5)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
require_once __DIR__ . '/../GeminiAi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = json_decode($raw, true) ?: $_POST;
|
||||
|
||||
$phone = preg_replace('/\D+/', '', $data['phone'] ?? '');
|
||||
$paymentMethod = strtolower(trim($data['payment_method'] ?? ''));
|
||||
$receiptImage = $data['receipt_image'] ?? '';
|
||||
$imageMimeType = $data['image_mime_type'] ?? 'image/jpeg';
|
||||
|
||||
if (empty($phone)) {
|
||||
printFailure('phone is required');
|
||||
exit;
|
||||
}
|
||||
|
||||
$paymentMethod = $paymentMethod ?: 'shamcash';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// HELPER: find pending invoice by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
function findPendingByPhone(PDO $con, string $table, string $phone, string $orderCol = 'created_at'): ?array
|
||||
{
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, amount, status, created_at
|
||||
FROM $table
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY $orderCol DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$phone]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function findLastCompletedByPhone(PDO $con, string $table, string $phone): ?array
|
||||
{
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, amount, status, created_at
|
||||
FROM $table
|
||||
WHERE phone = ? AND status = 'completed'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$phone]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SHAMCASH — AI Verification
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'shamcash') {
|
||||
$invoice = findPendingByPhone($con, 'invoices_shamcash', $phone);
|
||||
|
||||
if (!$invoice) {
|
||||
$lastCompleted = findLastCompletedByPhone($con, 'invoices_shamcash', $phone);
|
||||
if ($lastCompleted) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => 'آخر فاتورة لديك مكتملة بالفعل.',
|
||||
'invoice' => $lastCompleted,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'لا توجد فاتورة معلقة. يرجى إنشاء فاتورة عبر تطبيق Siro أولاً.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($receiptImage)) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'requires_image' => true,
|
||||
'message' => "تم العثور على فاتورة رقم {$invoice['invoice_number']} بمبلغ {$invoice['amount']} ل.س. يرجى إرسال صورة الإيصال.",
|
||||
'invoice' => $invoice,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── AI verify ───────────────────────────────────────────
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
if (empty($geminiKey)) {
|
||||
printFailure('AI verification service not configured');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment(
|
||||
$invoice['invoice_number'],
|
||||
$invoice['amount'],
|
||||
'ShamCash',
|
||||
'',
|
||||
$receiptImage
|
||||
);
|
||||
|
||||
if (!empty($aiResult['verified'])) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("
|
||||
UPDATE invoices_shamcash
|
||||
SET status = 'processing'
|
||||
WHERE id = ? AND status = 'pending'
|
||||
");
|
||||
$upd->execute([$invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
require_once __DIR__ . '/../shamcash/finalize_deposit.php';
|
||||
$finalized = finalizeShamCashDeposit($con, $invoice['id']);
|
||||
if ($finalized) {
|
||||
$con->commit();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => '✅ تم التحقق من عملية الدفع بنجاح! تم تحديث رصيد حسابك.',
|
||||
'invoice' => [
|
||||
'invoice_number' => $invoice['invoice_number'],
|
||||
'amount' => $invoice['amount'],
|
||||
'status' => 'completed',
|
||||
],
|
||||
'ai_reason' => $aiResult['reason'] ?? null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Verification passed but wallet update failed. Contact support.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'These funds have already been credited.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? 'لم يتم التأكيد';
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => "⚠️ $reason",
|
||||
'ai_reason' => $reason,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Nabeh ShamCash AI] " . $e->getMessage());
|
||||
printFailure('AI verification service error');
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CLIQ — AI Verification (same pattern as ShamCash)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'cliq') {
|
||||
$invoice = findPendingByPhone($con, 'cliq_invoices', $phone);
|
||||
|
||||
if (!$invoice) {
|
||||
$lastCompleted = findLastCompletedByPhone($con, 'cliq_invoices', $phone);
|
||||
if ($lastCompleted) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => 'آخر فاتورة لديك مكتملة بالفعل.',
|
||||
'invoice' => $lastCompleted,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'لا توجد فاتورة معلقة. يرجى إنشاء فاتورة عبر تطبيق Siro أولاً.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($receiptImage)) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'requires_image' => true,
|
||||
'message' => "تم العثور على فاتورة رقم {$invoice['invoice_number']} بمبلغ {$invoice['amount']} دينار. يرجى إرسال صورة الإيصال.",
|
||||
'invoice' => $invoice,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── AI verify ───────────────────────────────────────────
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
if (empty($geminiKey)) {
|
||||
printFailure('AI verification service not configured');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment(
|
||||
$invoice['invoice_number'],
|
||||
$invoice['amount'],
|
||||
'Cliq',
|
||||
'',
|
||||
$receiptImage
|
||||
);
|
||||
|
||||
if (!empty($aiResult['verified'])) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("
|
||||
UPDATE cliq_invoices
|
||||
SET status = 'completed', updated_at = NOW()
|
||||
WHERE id = ? AND status = 'pending'
|
||||
");
|
||||
$upd->execute([$invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
require_once __DIR__ . '/../cliq/finalize_payment.php';
|
||||
$finalized = finalizeClickPayment($con, $invoice['id']);
|
||||
if ($finalized['success']) {
|
||||
$con->commit();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => '✅ تم التحقق من عملية الدفع بنجاح! تم تحديث رصيد حسابك.',
|
||||
'invoice' => [
|
||||
'invoice_number' => $invoice['invoice_number'],
|
||||
'amount' => $invoice['amount'],
|
||||
'status' => 'completed',
|
||||
],
|
||||
'ai_reason' => $aiResult['reason'] ?? null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Verification passed but wallet update failed. Contact support.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'These funds have already been credited.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? 'لم يتم التأكيد';
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => "⚠️ $reason",
|
||||
'ai_reason' => $reason,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Nabeh Cliq AI] " . $e->getMessage());
|
||||
printFailure('AI verification service error');
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SMS / SYRIATEL — Status query by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'sms' || $paymentMethod === 'syriatel') {
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, user_phone AS method_phone, amount, status, created_at, ? AS payment_method
|
||||
FROM invoices_sms
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$paymentMethod, $phone]);
|
||||
$invoices = $stmt->fetchAll();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => !empty($invoices),
|
||||
'message' => empty($invoices) ? 'لا توجد فواتير معلقة.' : null,
|
||||
'invoices' => $invoices,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MTN — Status query by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'mtn') {
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, mtn_phone AS method_phone, amount, status,
|
||||
mtn_transaction_id AS transaction_id, created_at, updated_at AS paid_at, ? AS payment_method
|
||||
FROM mtn_invoices
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$paymentMethod, $phone]);
|
||||
$invoices = $stmt->fetchAll();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => !empty($invoices),
|
||||
'message' => empty($invoices) ? 'لا توجد فواتير معلقة.' : null,
|
||||
'invoices' => $invoices,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// UNKNOWN METHOD
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
printFailure("Invalid payment method: $paymentMethod");
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
//addPassengersWallet.php
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
$balance = filterRequest("balance");
|
||||
$token = filterRequest("token");
|
||||
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// Retrieve token details from the database securely and lock the row
|
||||
$stmt = $con->prepare("SELECT * FROM payment_tokens_passenger WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||
$stmt->execute([':token' => $token]);
|
||||
$tokenData = $stmt->fetch();
|
||||
|
||||
if ($tokenData) {
|
||||
// Insert into passengerWallet securely using prepared statements
|
||||
$sql = "INSERT INTO `passengerWallet` (`passenger_id`, `balance`) VALUES (:passenger_id, :balance)";
|
||||
$stmtInsert = $con->prepare($sql);
|
||||
$stmtInsert->execute([':passenger_id' => $passenger_id, ':balance' => $balance]);
|
||||
|
||||
if ($stmtInsert->rowCount() > 0) {
|
||||
// Mark the token as used
|
||||
$updateTokenStmt = $con->prepare("UPDATE payment_tokens_passenger SET isUsed = TRUE WHERE id = :tokenID");
|
||||
$updateTokenStmt->execute([':tokenID' => $tokenData['id']]);
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Wallet record created successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to create wallet record");
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Invalid or already used token");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[passengerWallet/add] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
//addPaymentTokenPassenger.php
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
// Check if required fields are present
|
||||
if ($passengerId === null || $amount === null) {
|
||||
printFailure("Missing required fields: passengerId and amount must be provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate the token using current time
|
||||
$token = generateSecureToken($passengerId, $amount, date('Y-m-d H:i:s', time()));
|
||||
|
||||
// Store the token in the database, using NOW() for dateCreated
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens_passenger (token, passengerId, dateCreated, amount) VALUES (?, ?, NOW(), ?)");
|
||||
|
||||
try {
|
||||
$stmt->execute([$token, $passengerId, $amount]);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
printSuccess($token);
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("[addPaymentTokenPassenger] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
|
||||
// Rest of your code including the generateSecureToken function...
|
||||
|
||||
// Rest of your code including the generateSecureToken function...
|
||||
|
||||
function generateSecureToken($passengerId, $amount, $dateCreated) {
|
||||
global $secretKey;
|
||||
// Concatenate the parameters
|
||||
$data = $passengerId . $amount . $dateCreated;
|
||||
|
||||
// Add the secret key from the environment variable
|
||||
$data .= $secretKey;
|
||||
|
||||
// Generate a hash
|
||||
$hash = hash('sha256', $data);
|
||||
|
||||
// Add some randomness
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
|
||||
// Combine hash and random bytes
|
||||
$token = $hash . $randomBytes;
|
||||
|
||||
// Truncate to a reasonable length (e.g., 64 characters)
|
||||
return substr($token, 0, 64);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* add_s2s_debt.php — Payment Server Endpoint
|
||||
*
|
||||
* Inserts passenger wallet credit/debit records (debt/penalty).
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerID = filterRequest("passengerID");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
if (empty($passengerID) || !isset($amount)) {
|
||||
printFailure("Missing required parameters: passengerID, amount");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
$sql = "INSERT INTO `passengerWallet` (
|
||||
`passenger_id`,
|
||||
`balance`
|
||||
) VALUES (
|
||||
:passengerID,
|
||||
:amount
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':passengerID' => $passengerID,
|
||||
':amount' => $amount
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("add_s2s_debt: " . $e->getMessage()); // logged server-side only
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
$sql = "DELETE FROM `passengerWallet` WHERE `id` = '$id'";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess($message = "Wallet record deleted successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "Failed to delete wallet record");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
passengerWallet.`id`,
|
||||
passengerWallet.`passenger_id`,
|
||||
SUM(passengerWallet.balance) AS total,
|
||||
passengers.first_name,
|
||||
passengers.last_name,
|
||||
passengers.phone,
|
||||
passengers.email
|
||||
FROM
|
||||
`passengerWallet`
|
||||
LEFT JOIN passengers ON passengers.id = passengerWallet.passenger_id
|
||||
GROUP BY
|
||||
passenger_id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
`id`,
|
||||
`passenger_id`,
|
||||
`balance`,
|
||||
`created_at`,
|
||||
`updated_at`,
|
||||
(
|
||||
SELECT
|
||||
SUM(balance)
|
||||
FROM
|
||||
passengerWallet
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'
|
||||
) AS total
|
||||
FROM
|
||||
`passengerWallet`
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'
|
||||
GROUP BY
|
||||
`passenger_id`,
|
||||
`id`;";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
passengerWallet.`id`,
|
||||
passengerWallet.balance,
|
||||
passengerWallet.`created_at`
|
||||
FROM
|
||||
`passengerWallet`
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'AND created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
|
||||
ORDER BY
|
||||
`passengerWallet`.`id`
|
||||
DESC";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
COALESCE(dummy.passenger_id, '$passenger_id') AS passenger_id,
|
||||
COALESCE(SUM(pw.balance), 0) AS total, -- Adjust column to represent payments
|
||||
COALESCE(p.first_name, '') AS first_name,
|
||||
COALESCE(p.last_name, '') AS last_name,
|
||||
COALESCE(p.phone, '') AS phone
|
||||
FROM
|
||||
(SELECT '$passenger_id' AS passenger_id) AS dummy
|
||||
LEFT JOIN `passengerWallet` pw ON pw.passenger_id = dummy.passenger_id
|
||||
LEFT JOIN passengers p ON p.id = dummy.passenger_id
|
||||
GROUP BY
|
||||
dummy.passenger_id, p.first_name, p.last_name, p.phone
|
||||
LIMIT 0, 25;
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== getenv('S2S_SHARED_KEY')) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
if (empty($passenger_id)) {
|
||||
printFailure("Missing required parameter: passenger_id");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtTotalWallet = $con->prepare("
|
||||
SELECT COALESCE(SUM(balance), 0)
|
||||
FROM `passengerWallet`
|
||||
WHERE passenger_id = :passenger_id
|
||||
");
|
||||
$stmtTotalWallet->execute([':passenger_id' => $passenger_id]);
|
||||
$totalWallet = (float)($stmtTotalWallet->fetchColumn() ?: 0.0);
|
||||
|
||||
printSuccess([
|
||||
"totalWallet" => $totalWallet
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log("[get_s2s_passenger_wallet] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// process_wait_compensation.php
|
||||
// يوضع هذا الملف على سيرفر المدفوعات (Payment Server)
|
||||
|
||||
include "../../connect.php"; // تأكد من مسار الاتصال
|
||||
|
||||
// 1. استقبال البيانات
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$passengerId = filterRequest("passenger_id");
|
||||
$amount = filterRequest("amount"); // المبلغ الموجب (للسائق)
|
||||
$amountPassenger= filterRequest("amount_passenger"); // المبلغ السالب (للراكب)
|
||||
$tokenDriver = filterRequest("token_driver");
|
||||
$tokenPassenger = filterRequest("token_passenger");
|
||||
$paymentMethod = "wait-cancel"; // أو يمكن استقباله من التطبيق
|
||||
|
||||
if (!$rideId || !$driverId || !$passengerId || !$amount || !$tokenDriver || !$tokenPassenger) {
|
||||
printFailure("Missing parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 🔥 بدء المعاملة المالية (Transaction)
|
||||
$con->beginTransaction();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 1: التحقق من التوكنات (Security Check)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// أ) فحص توكن السائق (مع FOR UPDATE)
|
||||
$stmtCheckD = $con->prepare("SELECT id FROM payment_tokens WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||
$stmtCheckD->execute([$tokenDriver]);
|
||||
$tokenDriverData = $stmtCheckD->fetch();
|
||||
|
||||
if (!$tokenDriverData) {
|
||||
throw new Exception("Invalid or used Driver Token");
|
||||
}
|
||||
|
||||
// ب) فحص توكن الراكب (مع FOR UPDATE)
|
||||
$stmtCheckP = $con->prepare("SELECT id FROM payment_tokens_passenger WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||
$stmtCheckP->execute([$tokenPassenger]);
|
||||
$tokenPassengerData = $stmtCheckP->fetch();
|
||||
|
||||
if (!$tokenPassengerData) {
|
||||
throw new Exception("Invalid or used Passenger Token");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 2: إضافة سجل النقاط (paymentsDriverPoints)
|
||||
// ---------------------------------------------------------
|
||||
// هذا الجدول يبدو أنه "سجل العمليات" الرئيسي
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints` (`amount`, `payment_method`, `driverID`) VALUES (?, ?, ?)";
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute([$amount, $paymentMethod, $driverId]);
|
||||
|
||||
// نحصل على ID العملية لنربطه بالمحفظة
|
||||
$paymentRecordID = $con->lastInsertId();
|
||||
|
||||
if ($stmtPoints->rowCount() == 0) {
|
||||
throw new Exception("Failed to insert into paymentsDriverPoints");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 3: إضافة الرصيد لمحفظة السائق (driverWallet)
|
||||
// ---------------------------------------------------------
|
||||
// نستخدم $paymentRecordID كمرجع للعملية
|
||||
$sqlWalletD = "INSERT INTO `driverWallet` (`driverID`, `paymentID`, `amount`, `paymentMethod`) VALUES (?, ?, ?, ?)";
|
||||
$stmtWalletD = $con->prepare($sqlWalletD);
|
||||
$stmtWalletD->execute([$driverId, $paymentRecordID, $amount, $paymentMethod]);
|
||||
|
||||
// حرق توكن السائق
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE id = ?")->execute([$tokenDriverData['id']]);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 4: خصم الرصيد من محفظة الراكب (passengerWallet)
|
||||
// ---------------------------------------------------------
|
||||
$sqlWalletP = "INSERT INTO `passengerWallet` (`passenger_id`, `balance`) VALUES (?, ?)";
|
||||
$stmtWalletP = $con->prepare($sqlWalletP);
|
||||
$stmtWalletP->execute([$passengerId, $amountPassenger]);
|
||||
|
||||
// حرق توكن الراكب
|
||||
$con->prepare("UPDATE payment_tokens_passenger SET isUsed = TRUE WHERE id = ?")->execute([$tokenPassengerData['id']]);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// إتمام العملية (Commit)
|
||||
// ---------------------------------------------------------
|
||||
$con->commit();
|
||||
printSuccess("Compensation processed successfully");
|
||||
|
||||
} catch (Exception $e) {
|
||||
// في حال حدوث أي خطأ، يتم التراجع عن كل العمليات السابقة
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[process_wait_compensation] " . $e->getMessage());
|
||||
printFailure("Transaction Failed");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$id = filterRequest("id");
|
||||
$balance = filterRequest("balance");
|
||||
|
||||
$sql = "UPDATE `passengerWallet` SET `balance` = '$balance' WHERE `id` = '$id'";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess($message = "Wallet record updated successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "Failed to update wallet record");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
// 1. احصل على AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1"); // ضع API Key الخاص بك هنا
|
||||
$email= filterRequest("amount");
|
||||
$first_name= filterRequest("first_name");
|
||||
$last_name= filterRequest("last_name");
|
||||
$phone_number= filterRequest("phone_number");
|
||||
$amount= filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
// printResponse("AUTH TOKEN RESPONSE", $response);
|
||||
|
||||
$auth_token = $response->token ?? null;
|
||||
if (!$auth_token) {
|
||||
die("❌ فشل الحصول على AUTH TOKEN!");
|
||||
}
|
||||
// $amount=$amount*100;
|
||||
// 2. أنشئ الطلب ORDER
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid(),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
// printResponse("ORDER RESPONSE", $response);
|
||||
|
||||
$order_id = $response->id ?? null;
|
||||
if (!$order_id) {
|
||||
die("❌ فشل إنشاء الطلب!");
|
||||
}
|
||||
$integration_id=getenv("paymobIntegratedIdCard");
|
||||
// 3. احصل على Payment Key
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" =>$first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "shobra",
|
||||
"street" => "Test St.",
|
||||
"building" => "1",
|
||||
"apartment" => "10",
|
||||
"floor" => "2",
|
||||
"postal_code" => "12345",
|
||||
"shipping_method"=> 'card'
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id, // ضع الـ Integration ID الصحيح
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
// printResponse("PAYMENT TOKEN RESPONSE", $response);
|
||||
|
||||
$payment_token = $response->token ?? null;
|
||||
if (!$payment_token) {
|
||||
die("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
}
|
||||
|
||||
// 4. إنشاء IFRAME URL
|
||||
$iframe_id = "837992"; // ضع الـ Iframe ID الصحيح
|
||||
$iframe_url = "https://accept.paymob.com/api/acceptance/iframes/$iframe_id?payment_token=$payment_token";
|
||||
if($payment_token){
|
||||
|
||||
printSuccess($iframe_url);
|
||||
}
|
||||
// دالة لطلب API عبر CURL
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return json_decode($response);
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/payment_verification.log"); // Define log file path
|
||||
|
||||
// Function to write to error log
|
||||
function logError($step, $message, $data = null) {
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logEntry = "[{$timestamp}] STEP {$step}: {$message}";
|
||||
|
||||
if ($data !== null) {
|
||||
$logEntry .= " | Data: " . json_encode($data);
|
||||
}
|
||||
|
||||
// Ensure log directory exists
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
// Append to log file
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Also log to PHP error log for server monitoring
|
||||
// error_log("PAYMENT_VERIFICATION: {$logEntry}");
|
||||
}
|
||||
|
||||
// Receive parameters from GET request
|
||||
$user_id = filterRequest("user_id");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
|
||||
// Log initial request
|
||||
// logError("0", "Request received", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
// Validate user_id and passengerId
|
||||
if (!$user_id || !$passengerId) {
|
||||
// logError("1", "Invalid parameters", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
printFailure("Invalid user ID or passenger ID.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Get the latest successful payment
|
||||
// logError("1", "Querying latest payment", ["user_id" => $user_id]);
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLog WHERE user_id = :user_id AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1");
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("1", "No payment found", ["user_id" => $user_id]);
|
||||
printFailure("No payment data found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("1", "Payment found", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "status" => $payment['status'],
|
||||
// "amount" => $payment['amount']/100 ?? 'unknown'
|
||||
// ]);
|
||||
|
||||
// Step 2: Check payment status
|
||||
if ($payment['status'] != 1) {
|
||||
// logError("2", "Payment not successful", ["status" => $payment['status']]);
|
||||
printFailure("Payment is not successful yet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("2", "Payment status verified", ["status" => $payment['status']]);
|
||||
|
||||
$amount = $payment['amount']/100; // Paid amount
|
||||
|
||||
// Step 3: Calculate bonus based on the paid amount
|
||||
// logError("3", "Calculating bonus", ["amount" => $amount]);
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
if ($finalAmount <= 0) {
|
||||
// logError("3", "Bonus calculation failed", [
|
||||
// "original_amount" => $amount,
|
||||
// "calculated_amount" => $finalAmount
|
||||
// ]);
|
||||
printFailure("Invalid amount for bonus calculation.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("3", "Bonus calculated", [
|
||||
// "original_amount" => $amount,
|
||||
// "final_amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
// // Step 4: Generate payment token
|
||||
// logError("4", "Generating payment token", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
// // Step 5: Add balance to passenger's wallet
|
||||
// logError("5", "Adding balance to passenger wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$walletResult = addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
|
||||
if (!$walletResult || !isset($walletResult['status']) || $walletResult['status'] != "success") {
|
||||
// logError("5", "Failed to add balance to passenger wallet", $walletResult);
|
||||
printFailure("Payment verified, but failed to add balance to passenger wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("5", "Balance added to passenger wallet", $walletResult);
|
||||
|
||||
// Step 6: Add balance to Siro wallet
|
||||
// logError("6", "Adding balance to Siro wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount,
|
||||
// "paymentMethod" => $paymentMethod
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
$siroWalletResult = addToSiroWallet($passengerId, $amount, $paymentMethod);
|
||||
|
||||
if (!$siroWalletResult || !isset($siroWalletResult['status']) || $siroWalletResult['status'] != "success") {
|
||||
// logError("6", "Failed to add balance to Siro wallet", $siroWalletResult);
|
||||
printFailure("Payment verified, but failed to add balance to Siro wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("6", "Balance added to Siro wallet", $siroWalletResult);
|
||||
|
||||
// // Final success
|
||||
// logError("7", "Process completed successfully", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "amount" => $finalAmount,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
printSuccess( "Payment data saved successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
logError("ERROR", "Database error: " . $e->getMessage());
|
||||
printFailure("Database error occurred.");
|
||||
} catch (Exception $e) {
|
||||
logError("ERROR", "General error: " . $e->getMessage());
|
||||
printFailure("An error occurred during payment verification.");
|
||||
}
|
||||
|
||||
// 🎯 Function to generate payment token with error logging
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
|
||||
$postData = [
|
||||
'passengerId' => $passengerId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("4.1", "cURL error in token generation", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("4.2", "HTTP error in token generation", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !isset($data['message'])) {
|
||||
logError("4.3", "Invalid response format in token generation", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data['message']; // ✅ Return token
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to passenger's wallet with error logging
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'passenger_id' => $passengerId,
|
||||
'balance' => $amount,
|
||||
'token' => $token
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("5.1", "cURL error in passenger wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("5.2", "HTTP error in passenger wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("5.3", "Invalid response format in passenger wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to Siro wallet with error logging
|
||||
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod) {
|
||||
|
||||
|
||||
// Generate a new token specifically for the Siro wallet
|
||||
$siroToken = generatePaymentToken($passengerId, $amount);
|
||||
|
||||
if (!$siroToken) {
|
||||
logError("6.0.1", "Failed to generate Siro token");
|
||||
return null;
|
||||
}
|
||||
|
||||
logError("6.0.2", "Generated new Siro token", [
|
||||
"token_length" => ($siroToken)
|
||||
]);
|
||||
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $siroToken, // Use the new Siro-specific token
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("6.1", "cURL error in Siro wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("6.2", "HTTP error in Siro wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("6.3", "Invalid response format in Siro wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
|
||||
// 🎯 Function to calculate bonus
|
||||
function calculateBonus($amount) {
|
||||
logError("3.1", "Bonus calculation input", ["amount" => $amount]);
|
||||
|
||||
$result = 0;
|
||||
if ($amount == 100) $result = 100;
|
||||
else if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
|
||||
logError("3.2", "Bonus calculation result", [
|
||||
"input" => $amount,
|
||||
"output" => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
Binary file not shown.
@@ -0,0 +1,100 @@
|
||||
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
// 1. احصل على AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1"); // ضع API Key الخاص بك هنا
|
||||
$email= filterRequest("amount");
|
||||
$first_name= filterRequest("first_name");
|
||||
$last_name= filterRequest("last_name");
|
||||
$phone_number= filterRequest("phone_number");
|
||||
$amount= filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
// printResponse("AUTH TOKEN RESPONSE", $response);
|
||||
|
||||
$auth_token = $response->token ?? null;
|
||||
if (!$auth_token) {
|
||||
die("❌ فشل الحصول على AUTH TOKEN!");
|
||||
}
|
||||
$amount=$amount*100;
|
||||
// 2. أنشئ الطلب ORDER
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid(),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
// printResponse("ORDER RESPONSE", $response);
|
||||
|
||||
$order_id = $response->id ?? null;
|
||||
if (!$order_id) {
|
||||
die("❌ فشل إنشاء الطلب!");
|
||||
}
|
||||
$integration_id=getenv("paymobIntegratedIdCardDriver");
|
||||
// 3. احصل على Payment Key
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" =>$first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "shobra",
|
||||
"street" => "Test St.",
|
||||
"building" => "1",
|
||||
"apartment" => "10",
|
||||
"floor" => "2",
|
||||
"postal_code" => "12345",
|
||||
"shipping_method"=> 'card'
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id, // ضع الـ Integration ID الصحيح
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
// printResponse("PAYMENT TOKEN RESPONSE", $response);
|
||||
|
||||
$payment_token = $response->token ?? null;
|
||||
if (!$payment_token) {
|
||||
die("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
}
|
||||
|
||||
// 4. إنشاء IFRAME URL
|
||||
$iframe_id = "837992"; // ضع الـ Iframe ID الصحيح
|
||||
$iframe_url = "https://accept.paymob.com/api/acceptance/iframes/$iframe_id?payment_token=$payment_token";
|
||||
if($payment_token){
|
||||
|
||||
printSuccess($iframe_url);
|
||||
}
|
||||
// دالة لطلب API عبر CURL
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return json_decode($response);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// 1️⃣ AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1");
|
||||
$integration_id = getenv("paymobIntegratedIdDriverWallet"); // 🔁 تأكد أنه خاص بالسائق
|
||||
|
||||
$email = filterRequest("email");
|
||||
$first_name = filterRequest("first_name");
|
||||
$last_name = filterRequest("last_name");
|
||||
$phone_number = filterRequest("phone_number"); // هاتف السائق
|
||||
$wallet_phone = '+2'.$phone_number;
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
$auth_token = $response->token ?? null;
|
||||
|
||||
if (!$auth_token) {
|
||||
error_log("❌ AUTH TOKEN retrieval failed!");
|
||||
die("❌ AUTH TOKEN retrieval failed!");
|
||||
}
|
||||
$amount=$amount*100;
|
||||
// 2️⃣ ORDER CREATE
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid("DRV_"),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
$order_id = $response->id ?? null;
|
||||
|
||||
if (!$order_id) {
|
||||
error_log("❌ Failed to create order for driver wallet!");
|
||||
die("❌ Failed to create order for driver wallet!");
|
||||
}
|
||||
|
||||
// 3️⃣ PAYMENT KEY
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" => $first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "Nasr City",
|
||||
"street" => "Driver Zone",
|
||||
"building" => "5",
|
||||
"apartment" => "D1",
|
||||
"floor" => "1",
|
||||
"postal_code" => "11765",
|
||||
"shipping_method" => "driver_wallet"
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
$payment_token = $response->token ?? null;
|
||||
|
||||
if (!$payment_token) {
|
||||
error_log("❌ Failed to get PAYMENT TOKEN for driver!");
|
||||
die("❌ Failed to get PAYMENT TOKEN for driver!");
|
||||
}
|
||||
|
||||
// 4️⃣ Final Step: Pay with Wallet
|
||||
$redirect_url = payWithWallet($payment_token, $wallet_phone);
|
||||
if ($redirect_url) {
|
||||
printSuccess($redirect_url);
|
||||
error_log("✅ redirect_url (driver): " . $redirect_url);
|
||||
} else {
|
||||
error_log("❌ Driver wallet payment failed!");
|
||||
printFailure("Payment verified, but failed to redirect.");
|
||||
}
|
||||
|
||||
|
||||
// 🔁 Shared helper functions
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
return json_decode($response);
|
||||
}
|
||||
|
||||
function payWithWallet($paymentToken, $walletPhone)
|
||||
{
|
||||
$url = "https://accept.paymob.com/api/acceptance/payments/pay";
|
||||
$data = [
|
||||
"source" => [
|
||||
"identifier" => $walletPhone,
|
||||
"subtype" => "WALLET"
|
||||
],
|
||||
"payment_token" => $paymentToken
|
||||
];
|
||||
$response = callAPI("POST", $url, json_encode($data));
|
||||
return $response->redirect_url ?? null;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
include "../../../jwtconnect.php";
|
||||
define('BASE_URL', 'https://wl.tripz-egypt.com/v1/main/ride');
|
||||
|
||||
try {
|
||||
$driverId = filterRequest('driverID');
|
||||
$user_id = filterRequest('user_id');
|
||||
$paymentMethod = filterRequest('paymentMethod');
|
||||
|
||||
if (empty($user_id) || empty($driverId)) {
|
||||
printFailure('Missing user_id or driverID.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1️⃣ تحقق من سجل الدفع خلال آخر دقيقتين
|
||||
$stmt = $con->prepare(
|
||||
'SELECT * FROM payment_log_driver
|
||||
WHERE user_id = :uid
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
|
||||
ORDER BY created_at DESC LIMIT 1'
|
||||
);
|
||||
$stmt->execute([':uid' => $user_id]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment || $payment['status'] != 1) {
|
||||
printFailure('No valid payment found.');
|
||||
exit;
|
||||
}
|
||||
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$bonus = match ((int)$originalAmount) {
|
||||
80 => 80.0,
|
||||
200 => 215.0,
|
||||
400 => 450.0,
|
||||
1000 => 1140.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// 2️⃣ توكن لـ DriverWallet
|
||||
$tokenDriver = generateToken($con, $driverId, $bonus);
|
||||
if (!$tokenDriver) {
|
||||
printFailure('Failed to generate token for driver wallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3️⃣ توكن مستقل لـ SiroWallet
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) {
|
||||
printFailure('Failed to generate token for siro wallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4️⃣ Payment ID
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonus, $paymentMethod);
|
||||
if (!$paymentID) {
|
||||
printFailure('Failed to generate payment ID.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5️⃣ Insert into driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonus,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($insertDriver->rowCount() === 0) {
|
||||
printFailure('Failed to insert into driverWallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 6️⃣ Update tokenDriver to isUsed = TRUE
|
||||
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenDriver->execute([':token' => $tokenDriver]);
|
||||
|
||||
// 7️⃣ Insert into siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
|
||||
// 8️⃣ Update tokenSiro to isUsed = TRUE
|
||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||
|
||||
// 🎉 Success response
|
||||
printSuccess([
|
||||
'message' => 'Payment verified and all wallets updated successfully.',
|
||||
'amount' => $originalAmount,
|
||||
'bonus' => $bonus,
|
||||
'paymentID' => $paymentID,
|
||||
'tokenUsed' => [
|
||||
'driverWalletToken' => $tokenDriver,
|
||||
'siroWalletToken' => $tokenSiro
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
printFailure("Server error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// ───────────────────────────
|
||||
// FUNCTIONS
|
||||
// ───────────────────────────
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
|
||||
// نفس المنطق من سكربتك
|
||||
$data = $driverId . $amount . time();
|
||||
$data .= $secretKey;
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
// تخزين التوكن في قاعدة البيانات
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount)
|
||||
VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([
|
||||
':token' => $token,
|
||||
':driverID' => $driverId,
|
||||
':amount' => $amount
|
||||
]);
|
||||
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $method): ?string {
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`)
|
||||
VALUES (:amount, :method, :driverID)");
|
||||
$stmt->execute([
|
||||
':driverID' => $driverId,
|
||||
':amount' => $amount,
|
||||
':method' => $method
|
||||
]);
|
||||
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// paymob_payout.php
|
||||
// سكريبت بي ات بي لمعاملات Paymob Payout (محفظة وبنك) بدون تخزين في قاعدة البيانات
|
||||
|
||||
declare(strict_types=1);
|
||||
include '../../../jwtconnect.php'; // يعطيك $con، filterRequest(), printSuccess(), printFailure()
|
||||
|
||||
// 1) جلب باراميترات الطلب عبر filterRequest
|
||||
$driverId = filterRequest('driverID');
|
||||
$amount = filterRequest('amount');
|
||||
$method = filterRequest('method'); // 'wallet' أو 'bank'
|
||||
$msisdn = filterRequest('msisdn');
|
||||
$bankCard = filterRequest('bankCard'); // يُستعمل عند method == 'bank'
|
||||
$bankCode = filterRequest('bankCode'); // يُستعمل عند method == 'bank'
|
||||
|
||||
if (empty($driverId) || empty($amount) || empty($method)) {
|
||||
printFailure('Missing parameters');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2) جلب بيانات Paymob من البيئة (Environment Variables)
|
||||
$pmUser = getenv('payMobOutUserName');
|
||||
$pmPass = getenv('payMobOutPassword');
|
||||
$pmClientId = getenv('PAYMOBOUTCLIENT_ID'); // من static const pmobid
|
||||
$pmSecret = getenv('PAYMOBOUTCLIENTSECRET'); // من static const pmobsec
|
||||
|
||||
// 3) دالة للحصول على OAuth Token من Paymob
|
||||
function fetchPaymobToken(string $user, string $pass, string $cid, string $secret): ?string {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/o/token/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
|
||||
CURLOPT_POSTFIELDS => http_build_query([
|
||||
'grant_type' => 'password',
|
||||
'username' => $user,
|
||||
'password' => $pass,
|
||||
'client_id' => $cid,
|
||||
'client_secret' => $secret,
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
if (!$resp) return null;
|
||||
$data = json_decode($resp, true);
|
||||
return $data['access_token'] ?? null;
|
||||
}
|
||||
|
||||
$oauthToken = fetchPaymobToken($pmUser, $pmPass, $pmClientId, $pmSecret);
|
||||
if (!$oauthToken) {
|
||||
printFailure('Failed to retrieve Paymob token');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4) دوال صرف الأموال
|
||||
function disburseWallet(string $token, string $amt, string $msisdn): array {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/disburse/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer $token",
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'amount' => $amt,
|
||||
'issuer' => 'wallet',
|
||||
'msisdn' => $msisdn,
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
return $resp ? json_decode($resp, true) : [];
|
||||
}
|
||||
|
||||
function disburseBank(string $token, string $amt, string $card, string $code): array {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/disburse/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer $token",
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'amount' => $amt,
|
||||
'issuer' => 'bank_card',
|
||||
'bank_card_number' => $card,
|
||||
'bank_code' => $code,
|
||||
'bank_transaction_type' => 'cash_transfer',
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
return $resp ? json_decode($resp, true) : [];
|
||||
}
|
||||
|
||||
// 5) استدعاء الدالة المناسبة وتنفيذ الصرف
|
||||
if ($method === 'wallet') {
|
||||
$result = disburseWallet($oauthToken, $amount, $msisdn);
|
||||
} else {
|
||||
$result = disburseBank($oauthToken, $amount, $bankCard, $bankCode);
|
||||
}
|
||||
|
||||
// 6) التحقق من نجاح الصرف وإرجاع النتيجة
|
||||
if (empty($result) || ($result['disbursement_status'] ?? '') !== 'successful') {
|
||||
printFailure('Disbursement failed');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 7) إرجاع التوكن والنتيجة للعميل بدون تخزين في DB
|
||||
printSuccess( $result);
|
||||
?>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
//
|
||||
/*طباعة الرسائل لأغراض التصحيح
|
||||
error_log("🔐 Message used for HMAC: " . $message);
|
||||
error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
error_log("📩 Received HMAC: " . $received_hmac);
|
||||
error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
*/
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
$user_id='+2'. $user_id;
|
||||
$amount=$amount/100;
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO payment_log_driver (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
error_log("Payment data saved successfully" . $status);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
//
|
||||
/*طباعة الرسائل لأغراض التصحيح
|
||||
error_log("🔐 Message used for HMAC: " . $message);
|
||||
error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
error_log("📩 Received HMAC: " . $received_hmac);
|
||||
error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
*/
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
$user_id='+'. $user_id;
|
||||
$amount=$amount/100;
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO payment_log_driver (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
error_log("Payment data saved successfully" . $status);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user