Files
law-api/app/Http/Controllers/api/SubscribePlanController.php
2026-08-16 17:33:19 +03:30

358 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\api;
use App\Http\Controllers\Controller;
use App\Models\PaymentTransaction;
use App\Models\SubscribePlan;
use App\Models\UserSubscriber;
use App\Services\AppMarketPurchaseVerifier;
use App\Traits\BaseApiResponse;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Shetabit\Multipay\Invoice;
use Shetabit\Payment\Facade\Payment;
class SubscribePlanController extends Controller
{
use BaseApiResponse;
public function __construct(private AppMarketPurchaseVerifier $marketVerifier)
{
}
public function index()
{
$subscribePlans = SubscribePlan::query()->where('is_active', true)->get()->map(function ($q) {
if ($q->is_free) {
if (auth()->user()->subscribePlans()->first() !== null) {
return null;
}
}
return [
'id' => $q->id,
'name' => $q->name,
'price' => $q->price,
'expired_day' => $q->expired_day,
'is_free' => $q->is_free == 1 ? true : false,
'type' => $q->type,
'transaction' => $q->transaction,
];
})->filter(function ($q) {
return $q != null;
});
return $this->success(array_values($subscribePlans->toArray()), 'Subscribe Plan', 'Subscribe Plan List');
}
public function subscribe(Request $request)
{
$request->validate([
'subscribe_plan_id' => 'required|exists:subscribe_plans,id',
]);
$subscribePlan = SubscribePlan::findOrFail($request->subscribe_plan_id);
$user = auth()->user();
$activeSubscription = $user->userSubscribers()
->where('expired_at', '>', now())
->first();
if ($activeSubscription && ! $activeSubscription?->subscribe?->is_free && ! $subscribePlan->is_free) {
return $this->failed(null, ['title' => 'اشتراک', 'message' => 'شما درحال حاظر اشتراک فعال دارید']);
}
if ($subscribePlan->is_free) {
$hasUsedFreePlan = $user->userSubscribers()
->whereHas('subscribe', function ($query) {
$query->where('is_free', true);
})->exists();
if ($hasUsedFreePlan) {
return $this->failed(null, ['title' => 'اشتراک', 'message' => 'شما قبلا اشتراک رایگان فعال کردید']);
}
$expiredAt = now()->addDays($subscribePlan->expired_day + 1);
$user->userSubscribers()->create([
'subscribe_plan_id' => $subscribePlan->id,
'expired_at' => $expiredAt,
]);
return $this->success(null, 'اشتراک', 'اشتراک رایگان شما فعال شد');
}
try {
$invoice = (new Invoice)->amount($subscribePlan->price);
$callback = 'https://ghaafapp.ir';
$payment = Payment::callbackUrl($callback)
->purchase($invoice, function ($driver, $transaction_id) use ($subscribePlan) {
PaymentTransaction::create([
'user_id' => auth()->user()->id,
'subscribe_plan_id' => $subscribePlan->id,
'transaction_id' => $transaction_id,
'amount' => $subscribePlan->price,
'status' => 'pending',
]);
})
->pay();
return $this->success(['url' => $payment]);
} catch (\Exception $e) {
Log::error('Payment initiation failed: '.$e->getMessage());
return $this->failed(null, 'درگاه پرداخت در دسترس نیست');
}
}
public function subscribe_new(Request $request)
{
$request->validate([
'subscribe_plan_id' => 'required|exists:subscribe_plans,id',
'subscription_id' => 'nullable',
'purchase_token' => 'nullable',
]);
$subscribePlan = SubscribePlan::findOrFail($request->subscribe_plan_id);
$user = auth()->user();
$activeSubscription = $user->userSubscribers()
->where('expired_at', '>', now())
->latest('expired_at')
->first();
if ($subscribePlan->is_free) {
$hasUsedFreePlan = $user->userSubscribers()
->whereHas('subscribe', function ($query) {
$query->where('is_free', true);
})
->exists();
if ($hasUsedFreePlan) {
return $this->failed(null, ['title' => 'اشتراک', 'message' => 'شما قبلا اشتراک رایگان فعال کردید']);
}
$expiredAt = $activeSubscription ? $activeSubscription->expired_at->addDays($subscribePlan->expired_day) : now()->addDays($subscribePlan->expired_day);
$user->userSubscribers()->create([
'subscribe_plan_id' => $subscribePlan->id,
'expired_at' => $expiredAt,
'is_free' => true,
]);
return $this->success(null, 'اشتراک', 'اشتراک رایگان فعال شد');
}
$subscription_id = $request->input('subscription_id');
$purchase_token = $request->input('purchase_token');
if (! $subscription_id || ! $purchase_token) {
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Invalid subscription details.']);
}
try {
$bazaarExpiredAt = $this->marketVerifier->verifyBazaarSubscription($subscription_id, $purchase_token);
if (! $bazaarExpiredAt) {
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Invalid or expired subscription.']);
}
$expiredAt = $activeSubscription ? $activeSubscription->expired_at->addDays($subscribePlan->expired_day) : $bazaarExpiredAt;
$user->userSubscribers()->whereHas('subscribe', function ($query) {
$query->where('is_free', true);
})
->update(['expired_at' => now()]);
$user->userSubscribers()->create([
'subscribe_plan_id' => $subscribePlan->id,
'expired_at' => $expiredAt,
'subscription_id' => $subscription_id,
'purchase_token' => $purchase_token,
'market_provider' => 'bazaar',
'last_verified_at' => now(),
'is_free' => false,
]);
return $this->success(null, 'اشتراک', 'اشتراک شما فعال شد');
} catch (\Exception $e) {
Log::error('Error in subscription', ['Error' => $e->getMessage()]);
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Failed to verify subscription.']);
}
}
public function subscribeMyket(Request $request)
{
$request->validate([
'subscribe_plan_id' => 'required|exists:subscribe_plans,id',
'sku_id' => 'required|string',
'token' => 'required_without_all:tokenId,token_id,purchase_token|string',
'tokenId' => 'required_without_all:token,token_id,purchase_token|string',
'token_id' => 'required_without_all:token,tokenId,purchase_token|string',
'purchase_token' => 'required_without_all:token,tokenId,token_id|string',
]);
$subscribePlan = SubscribePlan::findOrFail($request->subscribe_plan_id);
$user = auth()->user();
$token = $request->input('token')
?? $request->input('tokenId')
?? $request->input('token_id')
?? $request->input('purchase_token');
if ($subscribePlan->is_free) {
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Invalid subscription details.']);
}
try {
$purchase = $this->marketVerifier->verifyMyketProduct($request->sku_id, $token);
if (! $purchase) {
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Invalid or failed Myket purchase.']);
}
$expiredAt = $purchase['purchased_at']->copy()->addDays($subscribePlan->expired_day);
if ($expiredAt->lessThan(now())) {
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Invalid or expired subscription.']);
}
$user->userSubscribers()->whereHas('subscribe', function ($query) {
$query->where('is_free', true);
})->update(['expired_at' => now()]);
$user->userSubscribers()->updateOrCreate(
[
'market_provider' => 'myket',
'purchase_token' => $token,
],
[
'subscribe_plan_id' => $subscribePlan->id,
'expired_at' => $expiredAt,
'subscription_id' => $request->sku_id,
'product_id' => $request->sku_id,
'purchased_at' => $purchase['purchased_at'],
'last_verified_at' => now(),
'is_free' => false,
]
);
return $this->success(null, 'Subscribe Plan', 'Myket subscription successfully activated.');
} catch (\Exception $e) {
Log::error('Error in Myket subscription', ['Error' => $e->getMessage()]);
return $this->failed(null, ['title' => 'Subscribe Plan', 'message' => 'Failed to verify Myket subscription.']);
}
}
public function paymentCallback(Request $request)
{
try {
$url = 'http://bitpay.ir/payment/gateway-result-second';
$api = '066fd-d622e-690a9-be618-ddf02bc6059bbbd67c317bb340d1';
$trans_id = $request->input('trans_id');
$id_get = $request->input('id_get');
$result = $this->get($url, $api, $trans_id, $id_get);
$parseDecode = json_decode($result);
if ($parseDecode->status == 1) {
$transaction = PaymentTransaction::where('transaction_id', $id_get)->firstOrFail();
$transaction->update([
'status' => 'success',
]);
$expiredAt = now()->addDays($transaction->subscribePlan->expired_day);
UserSubscriber::where('user_id', $transaction->user_id)->delete();
$transaction->user->userSubscribers()->create([
'subscribe_plan_id' => $transaction->subscribe_plan_id,
'expired_at' => $expiredAt,
]);
return $this->success([], 'Payment Successful', 'Subscription successfully activated.');
}
return $this->failed([], ['title' => 'Payment Failed', 'message' => 'Payment verification failed.']);
} catch (\Exception $e) {
return $this->failed([], ['title' => 'Payment Failed', 'message' => 'Payment verification failed.']);
}
}
public function current()
{
$user = auth()->user();
// 1. Fetch all active subscriptions
$activeSubscribers = $user->userSubscribers()
->with('subscribe')
->where('expired_at', '>=', now())
->get();
if ($activeSubscribers->isNotEmpty()) {
// 2. Separate Paid plans from Free plans
$paidSubscriptions = $activeSubscribers->reject(function ($subscriber) {
return $subscriber->is_free || ($subscriber->subscribe && $subscriber->subscribe->is_free);
});
// 3. AUTHORITY RULE: Paid plans absolutely override Free plans!
if ($paidSubscriptions->isNotEmpty()) {
$latestSubscription = $paidSubscriptions->sortByDesc('expired_at')->first();
} else {
$latestSubscription = $activeSubscribers->sortByDesc('expired_at')->first();
}
// 4. Calculate accurate remaining days
$expiredDays = $latestSubscription->expired_at->diffInDays(now());
$isFree = (bool) (
$latestSubscription->is_free ||
($latestSubscription->subscribe && $latestSubscription->subscribe->is_free)
);
// 5. Format the exact same object expected by the frontend
$subscribePlanData = [
'id' => $latestSubscription->id,
'name' => $latestSubscription->subscribe->name ?? 'Subscription',
'price' => $latestSubscription->subscribe->price ?? 0,
'expired_day' => $expiredDays,
'is_free' => $isFree,
'expired_at' => $latestSubscription->expired_at->format('Y-m-d'),
];
return $this->success($subscribePlanData, 'Subscribe Plan', 'Current Subscribe Plan');
}
return $this->success(null, 'Subscribe Plan', 'No Subscribe Plan');
}
private function send($url, $api, $amount, $redirect, $factorId, $name, $email, $description)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, "api=$api&amount=$amount&redirect=$redirect&factorId=$factorId&name=$name&email=$email&description=$description");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
private function get($url, $api, $trans_id, $id_get)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, "api=$api&id_get=$id_get&trans_id=$trans_id&json=1");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
}