first commit

This commit is contained in:
2025-09-10 15:28:46 +03:30
commit 00edec179a
204 changed files with 21024 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use App\Jobs\SendSMSJob;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Console\Command;
class NotifySubEnd extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:notify-sub-end';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
User::with([
'products' => fn ($q) => $q->whereNotIn('gateway', [Transaction::GATEWAYS['cafe'], Transaction::GATEWAYS['myket']])->whereDate('expire_at', now()->subDay()),
'products.packageName',
])->whereHas('products', fn ($q) => $q->whereDate('expire_at', now()->subDay()))->chunkById(300, function ($users) {
foreach ($users as $user) {
$link = config('redirects.' . str_replace('.', '*', $user->products[0]->packageName->name));
dump($user->mobile);
SendSMSJob::dispatch([
'mobile' => $user->mobile,
'message' => "کاربرعزیز
اشتراک برنامه {$user->products[0]->packageName->title} برای شما به پایان رسید
برای تمدید کلیک کنید
$link"
]);
}
});
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Reminder;
use App\Jobs\SendPushJob;
use Carbon\Carbon;
class SendDueReminders extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'reminders:send-due';
/**
* The console command description.
*/
protected $description = 'Send push notifications for due reminders';
/**
* Execute the console command.
*/
public function handle(): int
{
$now = now();
$yesterday = now()->subDay();
$reminders = Reminder::with(['user', 'packageName'])->where(function ($query) use ($now) {
$query->where('repeatable', false)
->where('time', '<=', $now)
->whereNull('last_repeat');
})
->orWhere(function ($query) use ($now, $yesterday) {
$query->where('repeatable', true)
->where('time', '<=', $now)
->where(function ($sub) use ($now, $yesterday) {
$sub->whereNull('last_repeat')
->orWhere('last_repeat', '<=', $yesterday);
});
})
->get();
if ($reminders->isEmpty()) {
$this->info('No reminders due at this time.');
return Command::SUCCESS;
}
foreach ($reminders as $reminder) {
$user = $reminder->user;
$packageName = $reminder->packageName;
if ($reminder->last_repeat?->addDays($reminder->repeat_days)->isFuture()) {
continue;
}
SendPushJob::dispatch(
user: $user,
title: $reminder->title,
packageName: $packageName,
description: $reminder->description ?? '',
data: $reminder->data ?? []
);
$reminder->last_repeat = $now;
$reminder->save();
$this->info("Queued push notification for reminder {$reminder->id}.");
}
return Command::SUCCESS;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Helpers;
class MobileNumberHelper
{
public static function trim($mobileNumber)
{
$mobileNumber = (string) str_replace(" ", "", $mobileNumber);
if (strlen($mobileNumber) < 10) {
return null;
}
if (strlen($mobileNumber) == 10) {
$mobileNumber = '+98' . $mobileNumber;
} elseif (strlen($mobileNumber) == 11) {
$mobileNumber = '+98' . mb_substr($mobileNumber, 1);
} else {
$mobileNumber = '+' . $mobileNumber;
}
return (string)$mobileNumber;
}
public static function checkTrimmedNumber($mobileNumber)
{
$subString = substr($mobileNumber, 1);
if (strpos($mobileNumber, '+98') == 0 && is_numeric($subString) && strlen($subString) == 12) {
return true;
}
return false;
}
public static function checkMobileNumber($phone)
{
$pattern = '/(?:^(009809[\d]{9}|00989[\d]{9}|\+9809[\d]{9}|\+989[\d]{9}|989[\d]{9}|9809[\d]{9}|09[\d]{9}|9[\d]{9}))$/';
if (!preg_match($pattern, $phone)) {
return false;
}
return true;
}
public static function formatMobile($mobile)
{
if (!self::checkMobileNumber($mobile)) {
return null;
}
$patern = '/(?:^(009809|00989|\+9809|\+989|989|9809))/';
$mobile = trim(preg_replace($patern, '09', $mobile));
if ($mobile[0] == 9 && strlen($mobile) == 10) {
$mobile = 0 . $mobile;
}
return $mobile;
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Helpers;
class RedirectLinkHelper
{
public static function get($packageName, $platform = null)
{
$link = config('redirects.' . str_replace('.', '*', $packageName), "https://uzee.ir/$packageName");
if ($platform) {
$link .= ".$platform";
}
return $link;
}
}
+208
View File
@@ -0,0 +1,208 @@
<?php
namespace App\Http\Controllers;
use App\Models\OldPurchase;
use App\Models\PackageName;
use App\Models\Product;
use App\Models\User;
use App\Rules\MobileNumber;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class AdminController extends Controller
{
public function users(Request $request)
{
$data = $request->validate([
'per_page' => 'integer',
'mobile' => 'string',
'package_name' => 'string',
]);
$users = User::when(isset($data['package_name']), fn ($q) => $q->whereHas('packageNames', fn ($q2) => $q2->where('name', $data['package_name'])))
->when(isset($data['mobile']), fn ($q) => $q->where('mobile', $data['mobile']))
->paginate($data['per_page'] ?? 30);
return $users;
}
public function getUserTransactions(Request $request, $mobile)
{
$data = $request->validate([
'per_page' => 'integer'
]);
if (!$user = User::where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
$transactions = $user->transactions()->paginate($data['per_page'] ?? 30);
return $transactions;
}
public function getUserStatus(Request $request, $mobile)
{
$data = $request->validate([
'package_name' => 'string'
]);
if (isset($data['package_name']) && !$packageName = PackageName::with('products')->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!$user = User::with([
'products' => fn ($q) => $q->when(isset($data['package_name']), fn ($q1) => $q1->where('package_name_id', $packageName->id)),
'products.packageName'
])->where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
return $user;
}
public function getUserStatusV1(Request $request, $mobile)
{
$data = $request->validate([
'package_name' => 'string'
]);
$userMobile = $mobile;
if (isset($data['package_name']) && !$packageName = PackageName::with('products')
->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!OldPurchase::when(isset($data['package_name']), fn ($q) => $q->where('package_name', $data['package_name']))
->where('mobile', preg_replace('/09/', '989', $userMobile, 1))->first()) {
return response()->json([
'message' => 'user is not paid'
], 400);
}
return response()->json([
'message' => 'user is paid'
]);
}
public function subscribeUser(Request $request, $mobile)
{
$data = $request->validate([
'product_id' => 'integer|required'
]);
if (!$user = User::where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (!$product = Product::where('id', $data['product_id'])->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$product->buy($user);
return response()->json([
'message' => 'subscribed successfuly'
]);
}
public function unsubscribeUser(Request $request, $mobile)
{
$data = $request->validate([
'product_id' => 'integer|required'
]);
if (!$user = User::where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (!$product = Product::where('id', $data['product_id'])->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$user->products()->updateExistingPivot($product, ['expire_at' => now()->format('Y-m-d H:i:s')]);
return response()->json([
'message' => 'unsubscribed successfuly'
]);
}
public function deleteUser(Request $request, $mobile)
{
if (!$user = User::where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
$user->delete();
return response()->json([
'message' => 'user deleted'
]);
}
public function updateUserProfile(Request $request, $mobile)
{
$data = $request->validate([
'first_name' => 'string',
'last_name' => 'string',
'email' => 'email',
'mobile' => [new MobileNumber, 'string'],
'avatar' => 'image'
]);
if (!$subjectUser = User::where('mobile', $mobile)->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (isset($data['email'])) {
$user = User::where('email', $data['email'])->first();
if ($user && $data['email'] != $subjectUser->email) {
return response()->json([
'message' => 'user with given email exists'
], 400);
}
}
if (isset($data['mobile'])) {
$user = User::where('mobile', $data['mobile'])->first();
if ($user && $data['mobile'] != $subjectUser->mobile) {
return response()->json([
'message' => 'user with given email exists'
], 400);
}
}
if (isset($data['avatar'])) {
Storage::disk('public')->put("avatars/$user->uuid.png", file_get_contents($data['avatar']->path()));
$data['avatar'] = "storage/avatars/$user->uuid.png";
}
$subjectUser->update($data);
return response()->json([
'message' => 'user updated'
]);
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\RedirectLinkHelper;
use App\Models\Transaction;
use App\Modules\Gateway\AsanPardakht\AsanPardakhtApi;
use Illuminate\Http\Request;
class AsanPardakhtController extends Controller
{
public function pay(Request $request)
{
$data = $request->validate([
'token' => 'string|required',
'mobile' => 'string',
]);
return view('ap-form', ['token' => $data['token'], 'mobile' => $data['mobile']]);
}
public function checkTransaction(Request $request)
{
$data = $request->validate([
'localInvoiceId' => 'string|required',
'platform' => 'string'
]);
if (!$transaction = Transaction::with(['product.packageName', 'user'])->where('status', Transaction::STATUSES['pending'])->where('id', $data['localInvoiceId'])->first()) {
return view('status', [
'status' => false,
'redirect_link' => '#'
]);
}
$redirectLink = RedirectLinkHelper::get($transaction->product->packageName->name, $data['platform'] ?? null);
$response = AsanPardakhtApi::checkTransaction($transaction);
if (!$response['status']) {
return view('status', [
'status' => false,
'redirect_link' => $redirectLink
]);
}
$transaction->update(['status' => Transaction::STATUSES['success']]);
$transaction->product->buy($transaction->user, $transaction['authority'], Transaction::GATEWAYS['asanpardakht']);
return view('status', [
'status' => true,
'redirect_link' => $redirectLink
]);
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use App\Models\CafeConfig;
use Illuminate\Http\Request;
class CafeController extends Controller
{
public function index(Request $request)
{
return CafeConfig::get();
}
public function store(Request $request)
{
$data = $request->validate([
'client_id' => 'required|string',
'client_secret' => 'required|string',
'access_token' => 'required|string',
'refresh_token' => 'required|string',
]);
$CafeConfig = CafeConfig::create($data);
return $CafeConfig;
}
public function update(Request $request, $id)
{
$data = $request->validate([
'client_id' => 'required|string',
'client_secret' => 'required|string',
'access_token' => 'required|string',
'refresh_token' => 'required|string',
]);
if (!$cafeConfig = CafeConfig::where('id', $id)->first()) {
return response()->json([
'message' => 'cafe config not found'
], 404);
}
$cafeConfig->update($data);
return $cafeConfig;
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}
@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\RedirectLinkHelper;
use App\Models\Transaction;
use App\Modules\Gateway\Digipay\DigiPayApi;
use Illuminate\Http\Request;
class DigiPayController extends Controller
{
public function checkTransaction(Request $request)
{
$data = $request->validate([
'providerId' => 'string|required',
'result' => 'string|required',
'trackingCode' => 'string|required',
'amount' => 'string|required',
]);
if (!$transaction = Transaction::with(['product.packageName', 'user'])->where('uuid', $data['providerId'])->first()) {
return view('status', [
'status' => false,
'redirect_link' => '#'
]);
}
if ($data['result'] != 'SUCCESS') {
return view('status', [
'status' => false,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
$response = DigiPayApi::checkTransaction($data['trackingCode']);
if (!$response['status']) {
return view('status', [
'status' => false,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
$transaction->update(['status' => Transaction::STATUSES['success']]);
$transaction->product->buy($transaction->user, $transaction['authority'], Transaction::GATEWAYS['asanpardakht']);
return view('status', [
'status' => true,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
}
@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers;
use App\Models\OldPurchase;
use App\Models\PackageName;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class OldApprooController extends Controller
{
public function getUserUnConsumedPurchases(Request $request)
{
$data = $request->validate([
'package_name' => 'string|required'
]);
$userMobile = auth()->user()->mobile;
if (!$packageName = PackageName::with('products')->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!OldPurchase::where('package_name', $data['package_name'])->where('mobile', preg_replace('/09/', '989', $userMobile, 1))->first()) {
return response()->json([
'message' => 'user is not paid'
], 400);
}
return response()->json([
'message' => 'user is paid'
]);
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers;
use App\Models\CafeConfig;
use App\Models\PackageName;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class PackageNameController extends Controller
{
public function index(Request $request)
{
$packageNames = PackageName::get();
return $packageNames;
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'string|required',
'title' => 'string|required',
'avatar' => 'image',
'v1_identifier' => 'string|nullable',
'myket_access_token' => 'string|nullable',
'cafe_config_id' => 'integer|nullable',
'web_app_url' => 'string|nullable',
'tries' => 'integer',
]);
$otherData = $request->validate([
'firebase_json' => 'file'
]);
if ($packageName = PackageName::where('name', $data['name'])->first()) {
return response()->json([
'message' => 'package exists'
], 400);
}
if (isset($data['avatar'])) {
Storage::disk('public')->put("package-names/{$data['name']}.png", file_get_contents($data['avatar']->path()));
$data['image'] = "storage/package-names/{$data['name']}.png";
unset($data['avatar']);
}
if (isset($data['cafe_config_id']) && !$CafeConfig = CafeConfig::where('id', $data['cafe_config_id'])->first()) {
return response()->json([
'message' => 'cafe config not found'
], 404);
}
if (isset($otherData['firebase_json'])) {
$packageName->addMedia($otherData['firebase_json']->path())
->usingName($otherData['firebase_json'] ?? '')
->usingFileName($otherData['firebase_json']->getClientOriginalName())
->toMediaCollection('firebase_json');
}
$packageName = PackageName::create($data);
return $packageName;
}
public function update(Request $request, $name)
{
$data = $request->validate([
'title' => 'string|required',
'avatar' => 'image',
'v1_identifier' => 'string|nullable',
'myket_access_token' => 'string|nullable',
'cafe_config_id' => 'integer|nullable',
'web_app_url' => 'string|nullable',
'tries' => 'integer'
]);
$otherData = $request->validate([
'firebase_json' => 'file'
]);
if (isset($data['avatar'])) {
Storage::disk('public')->put("package-names/{$name}.png", file_get_contents($data['avatar']->path()));
$data['image'] = "storage/package-names/{$name}.png";
unset($data['avatar']);
}
if (isset($data['cafe_config_id']) && !$CafeConfig = CafeConfig::where('id', $data['cafe_config_id'])->first()) {
return response()->json([
'message' => 'cafe config not found'
], 404);
}
if (!$packageName = PackageName::where('name', $name)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$packageName->update($data);
if (isset($otherData['firebase_json'])) {
$packageName->addMedia($otherData['firebase_json']->path())
->usingName($otherData['firebase_json'] ?? '')
->usingFileName($otherData['firebase_json']->getClientOriginalName())
->toMediaCollection('firebase_json');
}
return $packageName;
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace App\Http\Controllers;
use App\Models\PackageName;
use App\Models\Product;
use App\Models\Transaction;
use App\Services\CafeService;
use App\Services\MyketService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Rule;
class ProductController extends Controller
{
public function index(Request $request, $packageName)
{
if (!$packageName = PackageName::with('products')->where('name', $packageName)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$products = $packageName->products;
return $products;
}
public function store(Request $request, $packageName)
{
$data = $request->validate([
'title' => 'string|required',
'price' => 'integer|required',
'type' => ['integer', Rule::in(Product::TYPES)],
]);
if (!$packageName = PackageName::with('products')->where('name', $packageName)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$product = $packageName->products()->create($data);
return $product;
}
public function update(Request $request, $packageName, $productId)
{
$data = $request->validate([
'title' => 'string|required',
'price' => 'integer|required',
'type' => ['integer', Rule::in(Product::TYPES)],
]);
if (!$packageName = PackageName::with('products')->where('name', $packageName)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!$product = Product::where('id', $productId)->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$product->update($data);
return $product;
}
public function delete($packageName, $productId)
{
if (!$packageName = PackageName::with('products')->where('name', $packageName)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!$product = Product::where('id', $productId)->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$product->delete();
return response()->json([
'message' => 'product deleted'
]);
}
public function subscribe(Request $request, $packageName, $productId)
{
$data = $request->validate([
'gateway' => 'string|required|in:cafe,myket',
'purchase_token' => 'string|required',
]);
if (!$packageName = PackageName::where('name', $packageName)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$user = auth()->user();
if (!$product = Product::with(['packageName.cafeConfig'])->where('id', $productId)->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
if ($data['gateway'] == 'zarinpal') {
if (!$transaction = Transaction::where('authority', $data['authority'])->first()) {
return response()->json([
'message' => 'transaction not found'
], 404);
}
if ($transaction->status == Transaction::STATUSES['consumed']) {
return response()->json([
'message' => 'already consumed'
], 400);
}
$merchantId = config('zarinpal.merchant_id');
$response = Http::retry(3, 100, throw: false)->post(config('zarinpal.url.verify'), [
'merchant_id' => $merchantId,
'amount' => $product['price'] * 10,
'authority' => $data['authority'],
]);
if ($response['data'] == [] || $response['data']['code'] == 101) {
return response()->json([
"message" => "failed",
"code" => strval($response['errors']['code'] ?? 101),
], 400);
}
}
if ($data['gateway'] == 'cafe') {
// $service = new CafeService($product->packageName->cafeConfig);
// $purchaseStatus = $service->checkPurchase($product, $data['purchase_token']);
// if (!$purchaseStatus['status']) {
// return response()->json([
// 'message' => $purchaseStatus['message'],
// ], 400);
// }
// $service->consumePurchase($product, $data['purchase_token']);
$user->transactions()->create([
'amount' => $product->price,
'authority' => $data['purchase_token'],
'product_id' => $product->id,
'status' => Transaction::STATUSES['consumed'],
'gateway' => Transaction::GATEWAYS[$data['gateway']],
]);
}
if ($data['gateway'] == 'myket') {
$service = new MyketService($product->packageName->cafeConfig);
$purchaseStatus = $service->checkPurchase($product, $data['purchase_token']);
if (!$purchaseStatus['status']) {
return response()->json([
'message' => $purchaseStatus['message'],
], 400);
}
$user->transactions()->create([
'amount' => $product->price,
'authority' => $data['purchase_token'],
'product_id' => $product->id,
'status' => Transaction::STATUSES['consumed'],
'gateway' => Transaction::GATEWAYS[$data['gateway']],
]);
}
$product->buy($user, $data['purchase_token'], Transaction::GATEWAYS[$data['gateway']]);
return response()->json([
'message' => 'subscribed successfuly'
]);
}
}
@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers;
use App\Models\PackageName;
use App\Models\Reminder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ReminderController extends Controller
{
public function index()
{
$reminders = Reminder::where('user_id', Auth::id())
->orderBy('time')
->get();
return response()->json($reminders);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:500',
'time' => 'required|date',
'repeatable' => 'boolean',
'repeat_days' => 'nullable|integer|min:1',
'data' => 'nullable|array',
]);
$packageNameData = $request->validate([
'package_name' => 'required|exists:package_names,name',
]);
if (!$packageName = PackageName::with('products')->where('name', $packageNameData['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$reminder = Reminder::create([...$data, 'package_name_id' => $packageName->id, 'user_id' => auth()->user()->id]);
return $reminder;
}
public function show(Reminder $reminder)
{
$this->authorizeReminder($reminder);
return response()->json($reminder);
}
public function update(Request $request, Reminder $reminder)
{
$this->authorizeReminder($reminder);
$data = $request->validate([
'title' => 'sometimes|string|max:255',
'description' => 'nullable|string|max:500',
'time' => 'sometimes|date',
'repeatable' => 'boolean',
'repeat_days' => 'nullable|integer|min:1',
'data' => 'nullable|array',
]);
$packageNameData = $request->validate([
'package_name' => 'required|exists:package_names,name',
]);
if (!$packageName = PackageName::with('products')->where('name', $packageNameData['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$reminder->update([...$data, 'package_name_id' => $packageName->id]);
return response()->json($reminder);
}
public function destroy(Reminder $reminder)
{
$this->authorizeReminder($reminder);
$reminder->delete();
return response()->json(null, 204);
}
private function authorizeReminder(Reminder $reminder)
{
if ($reminder->user_id !== Auth::id()) {
abort(403, 'Unauthorized action.');
}
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Transaction;
use App\Modules\Gateway\AsanPardakht\AsanPardakhtApi;
use App\Modules\Gateway\Digipay\DigiPayApi;
use App\Modules\Gateway\Zarinpal\ZarinpalApi;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class TransactionController extends Controller
{
public function index(Request $request)
{
$data = $request->validate([
'per_page' => 'integer'
]);
$transactions = auth()->user()->transactions()->paginate($data['per_page'] ?? 30);
return $transactions;
}
public function getGateway(Request $request)
{
$data = $request->validate([
'description' => 'required|string',
'product_id' => 'nullable|string',
'platform' => 'in:mobile,web,bot'
]);
if (isset($data['product_id']) && !$product = Product::where('id', $data['product_id'])->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$user = auth()->user();
if ($user->products()->where('products.id', $data['product_id'])->where('type', Product::TYPES['permanent'])->first()) {
return response()->json([
'message' => 'already have product'
], 400);
}
$transaction = $user->transactions()->create([
'amount' => floor($product->price * 1.1),
'product_id' => $data['product_id'],
'uuid' => Str::uuid()->toString()
]);
$gateway = Transaction::GATEWAYS['asanpardakht'];
$response = AsanPardakhtApi::getGateway($transaction);
if (!($response['status'] ?? false)) {
$response = ZarinpalApi::getGateway($transaction, $data['description']);
$gateway = Transaction::GATEWAYS['zarinpal'];
if (!($response['status'] ?? false)) {
$response = DigiPayApi::getGateway($transaction);
$gateway = Transaction::GATEWAYS['digipay'];
if (!($response['status'] ?? false)) {
return response()->json(["message" => "failed"], 400);
}
}
}
$transaction->gateway = $gateway;
$transaction->authority = $response['data']['authority'];
$transaction->save();
return response()->json([
'url' => $response['data']['url']
]);
}
}
+467
View File
@@ -0,0 +1,467 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\MobileNumberHelper;
use App\Jobs\SendSMSJob;
use App\Models\PackageName;
use App\Models\Product;
use App\Models\Transaction;
use App\Models\User;
use App\Rules\MobileNumber;
use App\Services\CafeService;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Http;
class UserController extends Controller
{
public function login(Request $request)
{
$data = $request->validate([
'auth' => 'string|required',
'password' => 'string|required',
'package_name' => 'string|required',
'fcm_token' => 'string|nullable'
]);
if (!$packageName = PackageName::where('name', $data['package_name'])->first()) {
return response()->json(['message' => 'package name not found'], 404);
}
$authType = filter_var($data['auth'], FILTER_VALIDATE_EMAIL) ? 'email' : 'mobile';
if ($authType == 'mobile') {
$data['auth'] = MobileNumberHelper::formatMobile($data['auth']);
}
$user = User::where($authType, $data['auth'])->first();
if (!$user || !Hash::check($data['password'], $user->password)) {
return response()->json(['message' => 'رمز عبور اشتباه است'], 404);
}
// Attach package if not exists
if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) {
$user->packageNames()->attach($packageName->id, [
'tries' => $packageName->tries,
'fcm_token' => $data['fcm_token'] ?? null,
]);
} else {
$user->packageNames()->updateExistingPivot($packageName->id, [
'fcm_token' => $data['fcm_token'] ?? null,
]);
}
$token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function resendEmailVerification(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
$user = User::where('email', $request->email)->first();
if (!$user) {
return response()->json(['message' => 'User not found'], 404);
}
if ($user->email_verified_at) {
return response()->json(['message' => 'Email is already verified'], 400);
}
// Generate new code
$code = rand(1000, 9999);
// Delete previous codes
\DB::table('email_verifications')->where('user_id', $user->id)->delete();
// Insert new verification code
\DB::table('email_verifications')->insert([
'user_id' => $user->id,
'code' => $code,
'expires_at' => now()->addMinutes(15),
'created_at' => now(),
'updated_at' => now(),
]);
// Send email
\Mail::to($user->email)->send(new \App\Mail\VerifyEmailCodeMail($code));
return response()->json([
'message' => 'Verification email resent successfully'
]);
}
public function register(Request $request)
{
$data = $request->validate([
'first_name' => 'string|nullable',
'last_name' => 'string|nullable',
'email' => 'email|required_without:mobile',
'password' => 'string|nullable|required_with:email',
'mobile' => ['string', new MobileNumber, 'nullable', 'required_without:email'],
'fcm_token' => 'string|nullable',
'package_name' => 'string|required'
]);
$packageNameValue = $data['package_name'];
unset($data['package_name']);
if (isset($data['mobile'])) {
$data['mobile'] = MobileNumberHelper::formatMobile($data['mobile']);
if (User::where('mobile', $data['mobile'])->exists()) {
return response()->json(['message' => 'Mobile number already exists'], 400);
}
}
if (isset($data['email']) && User::where('email', $data['email'])->exists()) {
return response()->json(['message' => 'Email already exists'], 400);
}
// ✅ Check if email can receive a message before creating the user
if (isset($data['email'])) {
$code = rand(1000, 9999);
try {
// Try sending email
\Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code));
} catch (\Exception $e) {
return response()->json([
'code' => $e->getCode(),
'error' => $e->getMessage(),
'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید',
], 400);
}
}
// Hash password
$data['password'] = isset($data['password'])
? $data['password']
: Str::random(8);
// Create user only if email sending didn't fail
$user = User::create($data);
// Attach package
if ($packageName = PackageName::where('name', $packageNameValue)->first()) {
$user->packageNames()->attach($packageName->id, [
'tries' => $packageName->tries,
'fcm_token' => $data['fcm_token'] ?? null,
]);
}
// Save email verification code after user is created
if (isset($data['email'])) {
\DB::table('email_verifications')->insert([
'user_id' => $user->id,
'code' => $code,
'expires_at' => now()->addMinutes(15),
'created_at' => now(),
'updated_at' => now(),
]);
}
// Create token
$token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken;
return response()->json([
'message' => 'User registered successfully.',
'user' => $user,
'token' => $token
]);
}
public function verifyEmailCode(Request $request)
{
$request->validate([
'email' => 'required|email',
'code' => 'required|string',
]);
$user = User::where('email', $request->email)->first();
if (!$user) {
return response()->json(['message' => 'User not found'], 404);
}
$record = \DB::table('email_verifications')
->where('user_id', $user->id)
->where('code', $request->code)
->where('expires_at', '>', now())
->first();
if (!$record) {
return response()->json(['message' => 'Invalid or expired code'], 400);
}
// Mark email as verified
$user->update(['email_verified_at' => now()]);
// Delete verification record
\DB::table('email_verifications')->where('id', $record->id)->delete();
$token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken;
return response()->json(['message' => 'Email verified successfully', 'token' => $token]);
}
public function loginOTP(Request $request)
{
$data = $request->validate([
'mobile' => ['string', new MobileNumber, 'required'],
'package_name' => 'string|required',
'source' => [Rule::in(PackageName::SOURCES), 'integer'],
'fcm_token' => 'string',
]);
if (!$packageName = PackageName::where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!$user = User::where('mobile', $data['mobile'])->first()) {
$user = User::create([
'mobile' => MobileNumberHelper::formatMobile($data['mobile']),
'password' => config('app.default_password'),
]);
}
if ($data['mobile'] != config('approo.admin_mobile')) {
$otpToken = rand(pow(10, 4), pow(10, 5) - 1);
$user->otpTokens()->create([
'token' => $otpToken
]);
$verify = Str::random(8);
SendSMSJob::dispatch([
'mobile' => $data['mobile'],
'message' => "کد ورود به برنامه: $otpToken
@{$packageName->web_app_url} #$otpToken
لغو 11"
]);
}
if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) {
$user->packageNames()->attach($packageName->id, [
'tries' => $packageName->tries,
'source' => $data['source'] ?? 0,
'fcm_token' => $data['fcm_token'] ?? null,
]);
} else {
$user->packageNames()->updateExistingPivot($packageName->id, [
'fcm_token' => $data['fcm_token'] ?? null,
]);
}
return response()->json([
'message' => 'otp sent',
'verify_code' => $verify ?? null
]);
}
public function checkOTP(Request $request)
{
$data = $request->validate([
'mobile' => ['string', new MobileNumber, 'required'],
'token' => 'string|required',
]);
if (!$user = User::where('mobile', $data['mobile'])->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
$otpToken = $user->otpTokens()->where('token', $data['token'])->where('created_at', '>', now()->subMinutes(15))->first();
if (!$otpToken && !($data['mobile'] == config('approo.admin_mobile') && $data['token'] == config('approo.admin_otp'))) {
return response()->json([
'message' => 'token not valid'
], 400);
}
$otpToken?->delete();
$token = '';
if ($user->is_admin) {
$token = $user->createToken('token', ['admin'])->plainTextToken;
} else {
$token = $user->createToken('token', ['user'])->plainTextToken;
}
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function updateProfile(Request $request)
{
$data = $request->validate([
'first_name' => 'string|nullable',
'last_name' => 'string|nullable',
'birthday' => 'date|nullable',
'gender' => ['integer', 'nullable', Rule::in(User::GENDERS)],
'email' => 'email|nullable',
'mobile' => [new MobileNumber, 'string'],
'avatar' => 'image'
]);
if (isset($data['email'])) {
$user = User::where('email', $data['email'])->first();
if ($user && $data['email'] != auth()->user()->email) {
return response()->json([
'message' => 'user with given email exists'
], 400);
}
}
if (isset($data['mobile'])) {
$user = User::where('mobile', $data['mobile'])->first();
if ($user && $data['mobile'] != auth()->user()->mobile) {
return response()->json([
'message' => 'user with given email exists'
], 400);
}
}
$user = auth()->user();
if (isset($data['avatar'])) {
Storage::disk('public')->put("avatars/$user->uuid.png", file_get_contents($data['avatar']->path()));
$data['avatar'] = "storage/avatars/$user->uuid.png";
}
$user->update($data);
return response()->json([
'user' => $user,
'message' => 'user updated'
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'user logged out'
]);
}
public function status(Request $request)
{
$data = $request->validate([
'package_name' => 'string|required'
]);
if (!$packageName = PackageName::with('cafeConfig')->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$user = auth()->user();
if ($user->mobile == config('approo.admin_mobile')) {
$user['products'] = Product::where('package_name_id', $packageName->id)->get();
} else {
// if ($packageName->cafeConfig) {
// $user['products'] = $user->products()->where('package_name_id', $packageName->id)->get();
// foreach ($user['products'] as $key => $product) {
// $service = new CafeService($packageName->cafeConfig);
// if ($product->pivot->gateway == Transaction::GATEWAYS['cafe'] && $product->pivot->purchase_token && $product->type != Product::TYPES['permanent']) {
// $activeSubscriptions = $service->checkSubscription($product, $product->pivot->purchase_token);
// if ($activeSubscriptions) {
// $expire = Carbon::createFromTimestamp($activeSubscriptions[0]['validUntilTimestampMsec'] / 1000);
// $user->products()->updateExistingPivot($product, ['expire_at' => $expire->format('Y-m-d H:i:s')]);
// } else {
// $user->products()->updateExistingPivot($product, ['expire_at' => now()->format('Y-m-d H:i:s')]);
// }
// }
// }
// }
$user['products'] = $user->products()
->where('package_name_id', $packageName->id)
->where(fn($q) => $q->where('user_product.expire_at', '>', now()->addHour())->orWhere('user_product.expire_at', null))
->get();
$user['package_name'] = $user->packageNames()
->where('package_names.id', $packageName->id)
->first();
}
return $user;
}
public function try(Request $request, $name)
{
$user = auth()->user();
if (!$packageName = $user->packageNames()->where('name', $name)->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if ($packageName->pivot->tries <= 0) {
return response()->json([
'message' => 'no more tries'
], 400);
}
$packageName->pivot->tries->decrement();
return response()->json([
'message' => 'success'
]);
}
public function googleLogin(Request $request)
{
$data = $request->validate([
'access_token' => 'string|required',
'package_name' => 'string|required',
'source' => [Rule::in(PackageName::SOURCES), 'integer']
]);
if (!$packageName = PackageName::where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
$response = Http::get("https://www.googleapis.com/oauth2/v3/userinfo", [
'access_token' => $data['access_token']
])->json();
if (!isset($response['email'])) {
return response()->json([
'message' => 'invalid token'
], 400);
}
if (!$user = User::where('email', $response['email'])->first()) {
$user = User::create([
'email' => $response['email'],
'password' => config('app.default_password'),
]);
}
if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) {
$user->packageNames()->attach($packageName->id, [
'tries' => $packageName->tries,
'source' => $data['source'] ?? 0,
]);
}
$token = $user->createToken('token', ['user'])->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class WalletController extends Controller
{
public function index()
{
return response()->json(["wallet" => auth()->user()->wallet ?? 0]);
}
public function charge(Request $request)
{
$data = $request->validate([
'amount' => 'required|integer',
'authority' => 'string|required',
]);
if ($data['authority'] != 'test') {
$merchantId = config('zarinpal.merchant_id');
$response = Http::retry(3, 100, throw: false)->post(config('zarinpal.url.verify'), [
'merchant_id' => $merchantId,
'amount' => $data['amount'] * 10,
'authority' => $data['authority'],
]);
if ($response['data'] == [] || $response['data']['code'] == 101) {
return response()->json([
"message" => "failed",
"code" => strval($response['errors']['code'] ?? 101),
], 400);
}
}
$user = auth()->user();
$user->wallet = ($user->wallet ?? 0) + $data['amount'];
$user->save();
$user->transactions()->where('authority', $data['authority'])->update(['status' => Transaction::STATUSES['success']]);
return response()->json([
"wallet" => $user->wallet,
"message" => "wallet charged succesfully",
'ref_id' => $response['data']['ref_id'] ?? null,
]);
}
public function getWalletAdmin(Request $request)
{
$data = $request->validate([
'mobile' => 'required'
]);
if (!$user = User::where('mobile', $data['mobile'])->first()) {
return response()->json(["message" => 'user not found'], 404);
}
return response()->json(["wallet" => $user->wallet ?? 0]);
}
public function chargeWalletAdmin(Request $request)
{
$data = $request->validate([
'amount' => 'required|integer',
'mobile' => 'required'
]);
if (!$user = User::where('mobile', $data['mobile'])->first()) {
return response()->json(["message" => 'user not found'], 404);
}
$user->wallet = ($user->wallet ?? 0) + $data['amount'];
$user->save();
return response()->json(["wallet" => $user->wallet, "message" => "wallet charged succesfully"]);
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\RedirectLinkHelper;
use App\Models\Transaction;
use App\Modules\Gateway\Zarinpal\ZarinpalApi;
use Illuminate\Http\Request;
class ZarinpalController extends Controller
{
public function checkTransaction(Request $request)
{
$data = $request->validate([
'Authority' => 'string|required',
'Status' => 'string|required',
]);
if (!$transaction = Transaction::with(['product.packageName', 'user'])->where('authority', $data['Authority'])->first()) {
return view('status', [
'status' => false,
'redirect_link' => '#'
]);
}
if ($data['Status'] != 'OK') {
return view('status', [
'status' => false,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
$response = ZarinpalApi::checkTransaction($transaction);
if (!$response['status']) {
return view('status', [
'status' => false,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
$transaction->update(['status' => Transaction::STATUSES['success']]);
$transaction->product->buy($transaction->user, $transaction['authority'], Transaction::GATEWAYS['asanpardakht']);
return view('status', [
'status' => true,
'redirect_link' => RedirectLinkHelper::get($transaction->product->packageName->name)
]);
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Http;
use App\Http\Middleware\AutoBan;
use App\Http\Middleware\BanBot;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
'ban-bot' => BanBot::class,
'auto-ban' => AutoBan::class,
];
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class AutoBan
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->ip() == '91.92.190.63') {
return $next($request);
}
$bannedIps = Cache::get('banned-ips', ['62.212.88.47', '5.182.44.142', '185.244.36.217', '185.142.158.194', '188.229.86.48', '108.181.126.147',
'94.182.91.226', '188.212.22.252', '185.55.225.6', '176.9.35.126', '93.115.223.250', '88.198.230.56',
'185.94.98.252', '168.119.213.43', '193.151.150.73', '46.4.97.122', '192.42.116.176', '31.40.216.202']);
if (in_array($request->ip(), [...$bannedIps])) {
abort(403, ".|.");
}
$ipTries = Cache::get($request->ip() . '-tries', 0);
Cache::set($request->ip() . '-tries', $ipTries + 1, 600);
if ($ipTries > 30) {
Cache::set('banned-ips', [...$bannedIps, $request->ip()]);
}
return $next($request);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BanBot
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->header('cf-worker')) {
return response()->json(['message' => 'unauthorized'], 403);
}
if (in_array($request->ip(), ['23.95.132.45'])) {
return response()->json(['message' => 'unauthorized'], 403);
}
return $next($request);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace App\Jobs;
use App\Models\PackageName;
use App\Models\Reminder;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\CloudMessage;
class SendPushJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public User $user,
public $title,
public PackageName $packageName,
public string $description = '',
public array $data = []
) {}
/**
* Execute the job.
*/
public function handle(): void
{
$firebaseFile = $this->getFirebaseJsonPath();
$factory = (new Factory)
->withServiceAccount($firebaseFile);
$push = $factory->createMessaging();
$message = CloudMessage::fromArray([
'token' => $this->user->packageNames()->where('package_names.id', $this->packageName->id)->first()->pivot->fcm_token,
'notification' => [
'title' => $this->title,
'body' => $this->description,
],
'data' => $this->data
]);
$push->send($message);
}
/**
* Get Firebase JSON file path from Media Library for the package.
*/
private function getFirebaseJsonPath(): ?string
{
$media = $this->packageName->getFirstMedia('firebase_json');
if (!$media) {
return null;
}
return $media->getPath();
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class SendSMSJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(private $data)
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$response = Http::connectTimeout(5)->timeout(5)->get(config('kave.url'), [
'receptor' => $this->data['mobile'],
'message' => $this->data['message'],
'date' => $this->data['date'] ?? null
]);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Storage;
use Spatie\Backup\Events\BackupZipWasCreated;
class MailSuccessfulBackup
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param BackupZipWasCreated $event
* @return void
*/
public function handle(BackupZipWasCreated $event)
{
$this->mailBackupFile($event->pathToZip);
}
public function mailBackupFile($path)
{
Storage::disk('google')->put('approo_v2-backup.zip', file_get_contents($path));
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class VerifyEmailCodeMail extends Mailable
{
use Queueable, SerializesModels;
public $code;
public function __construct($code)
{
$this->code = $code;
}
public function build()
{
return $this->subject('Your Email Verification Code')
->view('emails.verify-code')
->with(['code' => $this->code]);
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\ModelFilters;
use EloquentFilter\ModelFilter;
class PackageNameFilter extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relationMethod => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\ModelFilters;
use EloquentFilter\ModelFilter;
class ProductFilter extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relationMethod => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\ModelFilters;
use EloquentFilter\ModelFilter;
class TransactionFilter extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relationMethod => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\ModelFilters;
use EloquentFilter\ModelFilter;
class UserFilter extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relationMethod => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use App\Traits\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CafeConfig extends Model
{
use HasFactory, HasUuid;
protected $guarded = [];
public function packageNames()
{
return $this->hasMany(PackageName::class);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class OldPurchase extends Model
{
protected $guarded = [];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
class OtpTokens extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo(User::class);
}
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use App\Traits\HasUuid;
use DateTimeInterface;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class PackageName extends Model implements HasMedia
{
use HasUuid, Filterable, InteractsWithMedia;
protected $guarded = [];
const SOURCES = [
'unknown' => 0,
'google_play' => 1,
'cafe' => 2,
'myket' => 3,
'web' => 4,
];
public function registerMediaCollections(): void
{
$this->addMediaCollection('firebase_json')->singleFile();
}
public function users()
{
return $this->belongsToMany(User::class, 'user_package_name')->withPivot(['id', 'tries', 'source', 'fcm_token'])->withTimestamps();
}
public function getImageAttribute()
{
return url($this->attributes['image']);
}
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
public function products()
{
return $this->hasMany(Product::class);
}
public function cafeConfig()
{
return $this->belongsTo(CafeConfig::class);
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Models;
use App\Traits\HasUuid;
use Carbon\Carbon;
use DateTimeInterface;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasUuid, Filterable;
protected $guarded = [];
const TYPES = [
'permanent' => 1,
'yearly' => 2,
'6months' => 3,
'monthly' => 4,
];
public function users()
{
return $this->belongsToMany(User::class, 'user_product')->withPivot(['expire_at', 'purchase_token', 'gateway'])->withTimestamps();
}
public function packageName()
{
return $this->belongsTo(PackageName::class);
}
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
public function buy($user, $purchaseToken = null, $gateway = null)
{
$expireDate = now();
if ($purchaseUser = $this->users()->where('users.id', $user->id)->wherePivot('expire_at', '>=', now())->first()) {
$expireDate = Carbon::parse($purchaseUser->pivot->expire_at) ?? now();
}
$expireAt = match ($this->type) {
1 => null,
2 => $expireDate->addDays(30 * 12)->format('Y-m-d H:i:s'),
3 => $expireDate->addDays(30 * 6)->format('Y-m-d H:i:s'),
4 => $expireDate->addDays(30 * 1)->format('Y-m-d H:i:s'),
};
if ($this->users()->where('users.id', $user->id)->exists()) {
$this->users()->updateExistingPivot($user->id, [
'expire_at' => $expireAt,
'purchase_token' => $purchaseToken,
'gateway' => $gateway,
]);
} else {
$this->users()->attach($user->id, [
'expire_at' => $expireAt,
'purchase_token' => $purchaseToken,
'gateway' => $gateway,
]);
}
$user->transactions()->where('authority', $purchaseToken)->update(['status' => Transaction::STATUSES['consumed']]);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Reminder extends Model
{
use HasFactory;
protected $guarded = [];
protected $casts = [
'data' => 'array',
'last_repeat' => 'date',
];
public function packageName()
{
return $this->belongsTo(PackageName::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use App\Traits\HasUuid;
use DateTimeInterface;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Model;
class Transaction extends Model
{
use HasUuid, Filterable;
protected $guarded = [];
const STATUSES = [
'pending' => 1,
'success' => 2,
'consumed' => 3,
'failed' => 3,
];
const GATEWAYS = [
'asanpardakht' => 1,
'zarinpal' => 2,
'digipay' => 3,
'cafe' => 4,
'myket' => 5,
];
public function user()
{
return $this->belongsTo(User::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace App\Models;
use App\Traits\HasUuid;
use DateTimeInterface;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Support\Str;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasUuid, Filterable;
const GENDERS = [
'male' => 1,
'female' => 2,
];
protected $guarded = [];
protected $hidden = [
'password',
];
protected $appends = [
'full_name'
];
// protected static function booted()
// {
// static::creating(function ($user) {
// if (empty($user->uuid)) {
// $user->uuid = (string) Str::uuid();
// }
// });
// }
public function getFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
public function getAvatarAttribute()
{
return isset($this->attributes['avatar']) ? url($this->attributes['avatar']) : null;
}
public function setPasswordAttribute($password)
{
$this->attributes['password'] = Hash::make($password);
}
public function otpTokens()
{
return $this->hasMany(OtpTokens::class);
}
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function reminders()
{
return $this->hasMany(Reminder::class);
}
public function packageNames()
{
return $this->belongsToMany(PackageName::class, 'user_package_name')->withPivot(['id', 'tries', 'source', 'fcm_token'])->withTimestamps();
}
public function products()
{
return $this->belongsToMany(Product::class, 'user_product')->withPivot(['expire_at', 'purchase_token', 'gateway'])->withTimestamps();
}
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}
@@ -0,0 +1,126 @@
<?php
namespace App\Modules\Gateway\AsanPardakht;
use App\Models\Product;
use App\Models\Transaction;
use Illuminate\Support\Facades\Http;
class AsanPardakhtApi
{
public static function getGateway(Transaction $transaction)
{
$user = auth()->user();
$response = Http::connectTimeout(5)->timeout(5)/* ->retry(3, 100, throw: false) */
->acceptJson()
->contentType('application/json')
->withHeaders([
'usr' => config('asanpardakht.user'),
'pwd' => config('asanpardakht.password'),
])
->post(config('asanpardakht.url.request'), [
'merchantConfigurationId' => config('asanpardakht.merchant_config_id'),
'serviceTypeId' => 1,
'localInvoiceId' => $transaction->id,
'amountInRials' => $transaction->amount * 10,
'localDate' => now()->format('Ymd His'),
'additionalData' => json_encode([
'mobile' => $user->mobile
]),
'callbackURL' => "https://payment.vada.ir/api/asanpardakht/check-transaction?localInvoiceId=$transaction->id" . (request()->get('platform') ? '&platform=' . request()->get('platform') : ''),
'paymentId' => "0",
]);
if (!$response->successful()) {
return ["message" => "failed", 'status' => false];
}
return [
'data' => [
'url' => 'https://payment.vada.ir/api/asanpardakht/pay?token=' . $response->json() . '&mobile=' . $user->mobile,
'authority' => $response->json()
],
'status' => true
];
}
public static function checkTransaction(Transaction $transaction)
{
$transactionResponse = self::transactionResult($transaction);
if (!$transactionResponse) {
return [
'status' => false,
];
}
self::verify($transactionResponse['payGateTranID']);
self::settlement($transactionResponse['payGateTranID']);
return [
'status' => true,
];
}
public static function transactionResult(Transaction $transaction)
{
$response = Http::connectTimeout(5)->timeout(5)->retry(3, 100, throw: false)
->acceptJson()
->contentType('application/json')
->withHeaders([
'usr' => config('asanpardakht.user'),
'pwd' => config('asanpardakht.password'),
])
->get(config('asanpardakht.url.result'), [
'LocalInvoiceId' => $transaction->id,
'MerchantConfigurationId' => config('asanpardakht.merchant_config_id'),
]);
if (!$response->successful()) {
return false;
}
return $response->json();
}
public static function verify($payGateTranId)
{
$response = Http::connectTimeout(5)->timeout(5)->retry(3, 100, throw: false)
->acceptJson()
->contentType('application/json')
->withHeaders([
'usr' => config('asanpardakht.user'),
'pwd' => config('asanpardakht.password'),
])
->post(config('asanpardakht.url.verify'), [
'merchantConfigurationId' => config('asanpardakht.merchant_config_id'),
'payGateTranId' => (int) $payGateTranId,
]);
if (!$response->successful()) {
return false;
}
return $response->json();
}
public static function settlement($payGateTranId)
{
$response = Http::connectTimeout(5)->timeout(5)->retry(3, 100, throw: false)
->acceptJson()
->contentType('application/json')
->withHeaders([
'usr' => config('asanpardakht.user'),
'pwd' => config('asanpardakht.password'),
])
->post(config('asanpardakht.url.settlement'), [
'merchantConfigurationId' => config('asanpardakht.merchant_config_id'),
'payGateTranId' => (int) $payGateTranId,
]);
if (!$response->successful()) {
return false;
}
return $response->json();
}
}
@@ -0,0 +1,73 @@
<?php
namespace App\Modules\Gateway\Digipay;
use App\Models\Product;
use App\Models\Transaction;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class DigiPayApi
{
public static function getToken()
{
$token = Cache::remember('digipay_token', 3600, function () {
return Http::connectTimeout(5)->timeout(5)->retry(3, 100, throw: false)
->asForm()
->withHeaders([
'Authorization' => 'Basic ' . config('digipay.api_key')
])
->post(config('digipay.url.login'), [
'username' => config('digipay.username'),
'password' => config('digipay.password'),
'grant_type' => 'password',
])['access_token'];
});
return $token;
}
public static function getGateway(Transaction $transaction)
{
$user = auth()->user();
$response = Http::connectTimeout(5)->timeout(5)->retry(3, 100, throw: false)
->withToken(self::getToken())
->post(config('digipay.url.request'), [
'providerId' => $transaction->uuid,
'amount' => $transaction->amount * 10,
'userType' => 0,
'redirectUrl' => 'https://payment.vada.ir/api/digipay/check-transaction',
'cellNumber' => $user->mobile,
]);
if (!isset($response['payUrl'])) {
return ["message" => "failed", 'status' => false];
}
return [
'data' => [
'url' => $response['payUrl'],
'authority' => $response['ticket']
],
'status' => true
];
}
public static function checkTransaction($trackingCode)
{
$response = Http::retry(3, 100, throw: false)
->withToken(self::getToken())
->post(str_replace(':trackingCode', $trackingCode, config('digipay.url.verify')));
if ($response['result']['status'] != 0) {
return [
'status' => false,
];
}
return [
'status' => true,
];
}
}
@@ -0,0 +1,57 @@
<?php
namespace App\Modules\Gateway\Zarinpal;
use App\Models\Product;
use App\Models\Transaction;
use Illuminate\Support\Facades\Http;
class ZarinpalApi
{
public static function getGateway(Transaction $transaction, $description)
{
$user = auth()->user();
$merchantId = config('zarinpal.merchant_id');
$response = Http::connectTimeout(2)->timeout(2)->retry(3, 500, throw: false)->post(config('zarinpal.url.request'), [
'merchant_id' => $merchantId,
'amount' => $transaction->amount * 10,
'description' => $description,
'callback_url' => 'https://payment.vada.ir/api/zarinpal/check-transaction',
'metadata' => [
'mobile' => $user->mobile
],
]);
if ($response['data'] == []) {
return ["message" => "failed", 'status' => false];
}
return [
'data' => [
'url' => config('zarinpal.url.pay') . $response['data']['authority'],
'authority' => $response['data']['authority']
],
'status' => true
];
}
public static function checkTransaction(Transaction $transaction)
{
$merchantId = config('zarinpal.merchant_id');
$response = Http::retry(3, 500, throw: false)->post(config('zarinpal.url.verify'), [
'merchant_id' => $merchantId,
'amount' => $transaction->amount * 10,
'authority' => $transaction->authority,
]);
if ($response['data'] == [] || $response['data']['code'] == 101) {
return [
'status' => false,
];
}
return [
'status' => true,
];
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
//
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
//
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Providers;
use App\Listeners\MailSuccessfulBackup;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
\Spatie\Backup\Events\BackupZipWasCreated::class => [
MailSuccessfulBackup::class
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
//
}
/**
* Determine if events and listeners should be automatically discovered.
*/
public function shouldDiscoverEvents(): bool
{
return false;
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class GoogleDriveServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
\Illuminate\Support\Facades\Storage::extend('google', function ($app, $config) {
$client = new \Google\Client();
$client->setClientId($config['clientId']);
$client->setClientSecret($config['clientSecret']);
$client->refreshToken($config['refreshToken']);
$service = new \Google\Service\Drive($client);
$options = [];
if (isset($config['teamDriveId'])) {
$options['teamDriveId'] = $config['teamDriveId'];
}
$adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service, $config['folder'] ?? '/', $options);
$driver = new \League\Flysystem\Filesystem($adapter);
return new \Illuminate\Filesystem\FilesystemAdapter($driver, $adapter);
});
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to your application's "home" route.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*/
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
}
@@ -0,0 +1,94 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Telescope::night();
$this->hideSensitiveRequestDetails();
// Telescope::filter(function (IncomingEntry $entry) {
// if ($this->app->environment('local')) {
// return true;
// }
// return $entry->isReportableException() ||
// $entry->isFailedRequest() ||
// $entry->isFailedJob() ||
// $entry->isScheduledTask() ||
// $entry->hasMonitoredTag();
// });
Telescope::tag(function (IncomingEntry $entry) {
if ($entry->type === 'request') {
return ['status:' . $entry->content['response_status']];
} else {
return [];
}
});
Telescope::tag(function (IncomingEntry $entry) {
if ($entry->type === 'request' && $entry->user?->mobile)
return [$entry->user->mobile];
if ($entry->type === 'request' && $entry->user?->email)
return [$entry->user->email];
});
Telescope::tag(function (IncomingEntry $entry) {
if ($entry->type === 'request')
return [$entry->content['ip_address']];
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
return true;
// return in_array($user->email, [
// //
// ]);
});
}
protected function authorization()
{
$this->gate();
Telescope::auth(function ($request) {
return true;
});
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class MobileNumber implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $phone)
{
return self::checkMobileNumber($phone);
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'شماره موبایل معتبر نیست';
}
private static function checkMobileNumber($phone)
{
$pattern = '/(?:^(009809[\d]{9}|00989[\d]{9}|\+9809[\d]{9}|\+989[\d]{9}|989[\d]{9}|9809[\d]{9}|09[\d]{9}|9[\d]{9}))$/';
if (!preg_match($pattern, $phone)) {
return false;
}
return true;
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Services;
use App\Models\Product;
use Illuminate\Support\Facades\Http;
class CafeService
{
public function __construct(private $cafeConfig)
{
}
private function getToken()
{
if ($this->cafeConfig->token_refreshed_at > now()->subMinute(50)) {
return $this->cafeConfig->access_token;
}
$response = Http::asForm()->post('https://pardakht.cafebazaar.ir/devapi/v2/auth/token/', [
'grant_type' => 'refresh_token',
'client_id' => $this->cafeConfig->client_id,
'client_secret' => $this->cafeConfig->client_secret,
'refresh_token' => $this->cafeConfig->refresh_token,
]);
$response = json_decode($response, true);
if (isset($response['access_token'])) {
$this->cafeConfig->access_token = $response['access_token'];
$this->cafeConfig->token_refreshed_at = now();
$this->cafeConfig->save();
}
return $response['access_token'] ?? null;
}
private function sendRequest($url, $method, $params = [])
{
$response = Http::connectTimeout(2)->timeout(5)->retry(3, 500, throw: false)->send($method, $url, [
'headers' => [
'Authorization' => $this->getToken()
],
'form_params' => $params
]);
return json_decode($response, true);
}
public function checkPurchase($product, $token)
{
$response = $this->sendRequest("https://pardakht.cafebazaar.ir/devapi/v2/api/validate/{$product->packageName->name}/inapp/{$product->uuid}/purchases/$token/", 'GET');
// if ($response['consumptionState']) {
// return [
// 'status' => false,
// 'message' => 'already consumed',
// ];
// }
if ($response['purchaseState']) {
return [
'status' => false,
'message' => 'refunded',
];
}
return [
'status' => true
];
}
public function consumePurchase($product, $token)
{
if ($product->type == Product::TYPES['permanent']) {
$response = $this->sendRequest("https://pardakht.cafebazaar.ir/devapi/v2/api/consume/{$product->packageName->name}/purchases/", 'POST', ['token' => $token]);
}
}
public function checkSubscription($product, $token)
{
$response = $this->sendRequest("https://pardakht.cafebazaar.ir/devapi/v2/api/applications/{$product->packageName->name}/active-subscriptions/$token/", 'GET');
return $response['subscriptions'] ?? false;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Services;
use App\Models\Product;
use Illuminate\Support\Facades\Http;
class MyketService
{
private function sendRequest($url, $method, $params = [], $packageName)
{
$response = Http::send($method, $url, [
'headers' => [
'X-Access-Token' => $packageName->myket_access_token
],
'form_params' => $params
]);
return json_decode($response, true);
}
public function checkPurchase($product, $token)
{
$response = $this->sendRequest("https://developer.myket.ir/api/applications/{$product->packageName->name}/purchases/products/{$product->uuid}/tokens/$token", 'GET', [], $product->packageName);
if ($response['purchaseState']) {
return [
'status' => false,
'message' => 'refunded',
];
}
return [
'status' => true
];
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait HasUuid
{
public static function bootHasUuid(): void
{
static::creating(function ($model) {
if (!$model->uuid) {
$model->uuid = Str::uuid()->toString();
}
});
}
}