File "SubscriptionWebhookController.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/Exam/SubscriptionWebhookController.php
File size: 58.1 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace App\Http\Controllers;
use App\Models\AddonSubscription;
use App\Models\Package;
use App\Models\PaymentConfiguration;
use App\Models\PaymentTransaction;
use App\Models\Subscription;
use App\Models\SubscriptionBill;
use App\Models\SubscriptionFeature;
use App\Repositories\AddonSubscription\AddonSubscriptionInterface;
use App\Repositories\PaymentTransaction\PaymentTransactionInterface;
use App\Repositories\Subscription\SubscriptionInterface;
use App\Repositories\SubscriptionFeature\SubscriptionFeatureInterface;
use App\Services\CachingService;
use App\Services\SubscriptionService;
use Auth;
use Carbon\Carbon;
use DB;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Razorpay\Api\Api;
use Stripe\Exception\SignatureVerificationException;
use Throwable;
use UnexpectedValueException;
use Exception;
use App\Models\School;
use Illuminate\Support\Facades\Config;
class SubscriptionWebhookController extends Controller
{
//
private CachingService $cache;
private PaymentTransactionInterface $paymentTransaction;
private SubscriptionService $subscriptionService;
private SubscriptionInterface $subscription;
private SubscriptionFeatureInterface $subscriptionFeature;
private AddonSubscriptionInterface $addonSubscription;
public function __construct(CachingService $cachingService, PaymentTransactionInterface $paymentTransaction, SubscriptionService $subscriptionService, SubscriptionInterface $subscription, SubscriptionFeatureInterface $subscriptionFeature, AddonSubscriptionInterface $addonSubscription)
{
$this->cache = $cachingService;
$this->paymentTransaction = $paymentTransaction;
$this->subscriptionService = $subscriptionService;
$this->subscription = $subscription;
$this->subscriptionFeature = $subscriptionFeature;
$this->addonSubscription = $addonSubscription;
}
public function stripe(Request $request)
{
DB::setDefaultConnection('mysql');
$systemSettings = PaymentConfiguration::where('school_id',NULL)->where('payment_method','Stripe')->first();
$endpoint_secret = $systemSettings->webhook_secret_key;
$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
Log::info("Stripe Webhook Event: " . json_encode($event));
} catch (UnexpectedValueException $e) {
// Invalid payload
Log::error("Payload Mismatch");
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
Log::error("Signature Verification Failed");
http_response_code(400);
exit();
}
$transaction_id = $event->data->object->id;
$paymentTransaction = PaymentTransaction::where('order_id',$transaction_id)->first();
if ($paymentTransaction) {
$transaction_id = $paymentTransaction->id;
switch ($event->type) {
case 'payment_intent.succeeded':
Log::error($transaction_id);
$paymentTransactionData = $this->paymentTransaction->findById($transaction_id);
if (!empty($paymentTransactionData)) {
if ($paymentTransactionData->status != 1) {
$school_id = $paymentTransactionData->school_id;
$this->paymentTransaction->update($transaction_id,['payment_status' => "succeed",'school_id' => $school_id]);
Log::error("Payment Success");
}else{
Log::error("Transaction Already Successes --->");
break;
}
}else {
Log::error("Payment Transaction id not found --->");
break;
}
http_response_code(200);
break;
case 'payment_intent.payment_failed':
$paymentTransactionData = $this->paymentTransaction->findById($transaction_id);
if (!empty($paymentTransactionData)) {
if ($paymentTransactionData->status != 1) {
$school_id = $paymentTransactionData->school_id;
$this->paymentTransaction->update($transaction_id,['payment_status' => "failed",'school_id' => $school_id]);
http_response_code(400);
break;
}
}else {
Log::error("Payment Transaction id not found --->");
break;
}
case 'charge.succeeded':
Log::error($transaction_id);
$paymentTransactionData = $this->paymentTransaction->findById($transaction_id);
if (!empty($paymentTransactionData)) {
if ($paymentTransactionData->status != 1) {
$school_id = $paymentTransactionData->school_id;
$this->paymentTransaction->update($transaction_id,['payment_status' => "succeed",'school_id' => $school_id]);
}else{
Log::error("Transaction Already Successes --->");
break;
}
}else {
Log::error("Payment Transaction id not found --->");
break;
}
http_response_code(200);
break;
default:
// Unexpected event type
Log::error('Received unknown event type');
}
}
// End Stripe
}
public function razorpay(Request $request)
{
Log::info('Called');
$webhookBody = file_get_contents('php://input');
try {
$data = json_decode($webhookBody, false, 512, JSON_THROW_ON_ERROR);
Log::info("Razorpay Webhook Data : ", [$data]);
$payload = $request->all();
// Log::info("Payload", $payload);
$data = (object)$payload;
$metadata = $data->payload['payment']['entity']['notes'];
// You can find your endpoint's secret in your webhook settings
DB::setDefaultConnection('mysql');
$paymentConfiguration = PaymentConfiguration::select('webhook_secret_key')->where('payment_method', 'razorpay')->where('school_id', null)->first();
$webhookSecret = $paymentConfiguration['webhook_secret_key'];
$webhookPublic = $paymentConfiguration["webhook_public_key"];
$api = new Api($webhookPublic, $webhookSecret);
$new_subscription = '';
// $metadata = $data->payload->payment->entity->notes;
Log::info($metadata);
$metadata = json_decode(json_encode($metadata));
if ($metadata && isset($data->event) && $data->event == 'payment.captured') {
//checks the signature
// $expectedSignature = hash_hmac("SHA256", $webhookBody, $webhookSecret);
// $api->utility->verifyWebhookSignature($webhookBody, $expectedSignature, $webhookSecret);
$paymentTransactionData = PaymentTransaction::where('id', $metadata->payment_transaction_id)->first();
if ($paymentTransactionData == null) {
Log::error("Razorpay Webhook : Payment Transaction id not found");
}
if ($paymentTransactionData && $paymentTransactionData->payment_status == "succeed") {
Log::info("Razorpay Webhook : Transaction Already Succeed");
} else {
DB::beginTransaction();
$paymentTransactionStatus = PaymentTransaction::find($metadata->payment_transaction_id);
if ($paymentTransactionStatus) {
$paymentTransactionStatus->payment_status = "succeed";
$paymentTransactionStatus->save();
}
// Addon
if ($metadata->type == 'addon') {
$addon_data = [
'subscription_id' => $metadata->subscription_id,
'school_id' => $metadata->school_id,
'feature_id' => $metadata->feature_id,
'price' => $metadata->amount,
'start_date' => Carbon::now(),
'end_date' => $metadata->end_date,
'status' => 1,
'payment_transaction_id' => $metadata->payment_transaction_id,
];
AddonSubscription::create($addon_data);
}
// Package
if ($metadata->type == 'package') {
// New package subscription
if ($metadata->package_type == 'new') {
$new_subscription = $this->subscriptionService->createSubscription($metadata->package_id, $metadata->school_id, null, 1);
}
// Upcoming prepaid plan
if ($metadata->package_type == 'upcoming') {
$active_plan = $this->subscriptionService->active_subscription($metadata->school_id);
$package = Package::find($metadata->package_id);
$start_date = Carbon::parse($active_plan->end_date)->addDays()->format('Y-m-d');
$end_date = Carbon::parse($start_date)->addDays(($package->days - 1))->format('Y-m-d');
$subscription_data = [
'package_id' => $package->id,
'name' => $package->name,
'student_charge' => $package->student_charge,
'staff_charge' => $package->staff_charge,
'start_date' => $start_date,
'end_date' => $end_date,
'package_type' => $package->type,
'no_of_students' => $package->no_of_students,
'no_of_staffs' => $package->no_of_staffs,
'billing_cycle' => $package->days,
'school_id' => $metadata->school_id,
'charges' => $package->charges
];
if ($active_plan->id == $metadata->subscription_id) {
// Same upcoming plan
$new_subscription = Subscription::create($subscription_data);
} else {
// Already set, update records
$new_subscription = Subscription::find($metadata->subscription_id)->update($subscription_data);
$new_subscription = Subscription::find($metadata->subscription_id);
}
// Add features
$subscription_features = array();
foreach ($new_subscription->package->package_feature as $key => $feature) {
$subscription_features[] = [
'subscription_id' => $new_subscription->id,
'feature_id' => $feature->feature_id
];
}
SubscriptionFeature::upsert($subscription_features, ['subscription_id', 'feature_id'], ['subscription_id', 'feature_id']);
// Generate bill
$systemSettings = $this->cache->getSystemSettings();
$subscription_bill = [
'subscription_id' => $new_subscription->id,
'amount' => $new_subscription->charges,
'total_student' => 0,
'total_staff' => 0,
'due_date' => Carbon::now()->addDays($systemSettings['additional_billing_days'])->format('Y-m-d'),
'school_id' => $metadata->school_id,
'payment_transaction_id' => $metadata->payment_transaction_id
];
// Create bill for active plan
SubscriptionBill::create($subscription_bill);
}
// Immediate change current package
if ($metadata->package_type == 'immediate') {
// Create current subscription bill
// Get current plan
$subscription = $this->subscriptionService->active_subscription($metadata->school_id);
// Postpaid plan generate bill
if ($subscription->package_type == 1) {
// Create current subscription plan bill
$this->subscriptionService->createSubscriptionBill($subscription, null);
}
$current_subscription_expiry = Subscription::find($subscription->id)->update(['end_date' => Carbon::now()->format('Y-m-d')]);
$current_subscription_expiry = Subscription::find($subscription->id);
Log::info('I am here................');
$this->subscriptionFeature->builder()->where('subscription_id', $subscription->id)->delete();
// Delete upcoming
$this->subscription->builder()->with('package')->doesntHave('subscription_bill')->whereDate('start_date', '>', $subscription->end_date)->delete();
// Delete addons
$addons = $this->addonSubscription->builder()->where('subscription_id', $subscription->id)->get();
$soft_delete_addon = array();
foreach ($addons as $key => $addon) {
AddonSubscription::find($addon->id)->update(['end_date' => $current_subscription_expiry->end_date]);
$soft_delete_addon[] = $addon->id;
}
$this->addonSubscription->builder()->whereIn('id', $soft_delete_addon)->delete();
// Set new plan
$package = Package::find($metadata->package_id);
$start_date = Carbon::now();
$end_date = Carbon::now()->addDays(($package->days - 1))->format('Y-m-d');
$subscription_data = [
'package_id' => $package->id,
'name' => $package->name,
'student_charge' => $package->student_charge,
'staff_charge' => $package->staff_charge,
'start_date' => $start_date,
'end_date' => $end_date,
'package_type' => $package->type,
'no_of_students' => $package->no_of_students,
'no_of_staffs' => $package->no_of_staffs,
'billing_cycle' => $package->days,
'school_id' => $metadata->school_id,
'charges' => $package->charges
];
$new_subscription = Subscription::create($subscription_data);
// Add features
$subscription_features = array();
foreach ($new_subscription->package->package_feature as $key => $feature) {
$subscription_features[] = [
'subscription_id' => $new_subscription->id,
'feature_id' => $feature->feature_id
];
}
SubscriptionFeature::upsert($subscription_features, ['subscription_id', 'feature_id'], ['subscription_id', 'feature_id']);
// Generate bill
$systemSettings = $this->cache->getSystemSettings();
$subscription_bill = [
'subscription_id' => $new_subscription->id,
'amount' => $new_subscription->charges,
'total_student' => 0,
'total_staff' => 0,
'due_date' => Carbon::now()->addDays($systemSettings['additional_billing_days'])->format('Y-m-d'),
'school_id' => $metadata->school_id,
'payment_transaction_id' => $metadata->payment_transaction_id
];
// Create bill for active plan
SubscriptionBill::create($subscription_bill);
}
if ($new_subscription && $metadata->package_type != 'upcoming' && $metadata->package_type != 'immediate') {
if ($new_subscription->subscription_bill && $new_subscription->subscription_bill->id) {
SubscriptionBill::find($new_subscription->subscription_bill->id)->update(['payment_transaction_id' => $metadata->payment_transaction_id]);
}
}
if ($metadata->subscription_id && $metadata->package_type != 'upcoming' && $metadata->package_type != 'immediate') {
// Check if the SubscriptionBill exists before attempting to find it
$subscriptionBill = SubscriptionBill::find($metadata->subscription_id);
if ($subscriptionBill) {
$subscriptionBill->update(['payment_transaction_id' => $metadata->payment_transaction_id]);
} else {
Log::error("SubscriptionBill with ID {$metadata->subscription_id} not found");
}
}
}
}
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.FEATURES'),$metadata->school_id);
Log::info("Razorpay Webhook : payment.captured");
http_response_code(200);
DB::commit();
} elseif ($metadata && isset($data->event) && $data->event == 'payment.failed') {
$paymentTransactionData = PaymentTransaction::find($metadata->payment_transaction_id);
if (!$paymentTransactionData) {
Log::error("Razorpay Webhook : Payment Transaction id not found --->");
}
PaymentTransaction::find($metadata->payment_transaction_id)->update(['payment_status' => "failed"]);
http_response_code(400);
} elseif (isset($data->event) && $data->event == 'payment.authorized') {
Log::error('Razorpay Webhook : payment.authorized');
http_response_code(200);
}
else {
Log::error('Razorpay Webhook : Received unknown event type');
}
} catch (UnexpectedValueException) {
// Invalid payload
echo "Razorpay Webhook : Payload Mismatch";
Log::error("Razorpay : Payload Mismatch");
http_response_code(400);
exit();
} catch (SignatureVerificationException) {
// Invalid signature
echo "Razorpay Webhook : Signature Verification Failed";
Log::error("Razorpay Webhook : Signature Verification Failed");
http_response_code(400);
exit();
} catch(Throwable $e) {
DB::rollBack();
Log::error("Razorpay Webhook : Error occurred", [$e->getMessage() . ' --> ' . $e->getFile() . ' At Line : ' . $e->getLine()]);
http_response_code(400);
exit();
}
Log::error('Webhook class');
}
public function flutterwave(Request $request)
{
Log::info('Flutterwave Webhook Called');
$webhookBody = file_get_contents('php://input');
try {
$data = json_decode($webhookBody, true);
Log::info("Flutterwave Webhook Data:", [$data]);
if (!$data) {
throw new Exception('Invalid webhook payload');
}
// Extract basic information from webhook
$event = $data['event'] ?? '';
$txRef = $data['data']['tx_ref'] ?? '';
$amount = $data['data']['amount'] ?? 0;
$status = $data['data']['status'] ?? '';
$customerEmail = $data['data']['customer']['email'] ?? '';
if (empty($txRef)) {
throw new Exception('Transaction reference not found in webhook data');
}
Log::info("Processing Flutterwave tx_ref: {$txRef}, event: {$event}, status: {$status}");
// Extract metadata from the webhook data
$metadata = $data['meta_data'];
// Log metadata
Log::info("Original metadata:", ['metadata' => $metadata]);
// Extract school_id from metadata
if (is_string($metadata)) {
// Try to parse JSON string metadata
$metadataObj = json_decode($metadata, true);
if ($metadataObj) {
$metadata = $metadataObj;
}
}
// Ensure metadata is an array
if (!is_array($metadata)) {
$metadata = [];
}
// Try to find the school_id in other places if not in metadata
$schoolId = $metadata['school_id'] ?? null;
// Find transaction in database
DB::setDefaultConnection('mysql');
$paymentTransaction = PaymentTransaction::where('order_id', $txRef)->first();
if (!$paymentTransaction) {
Log::info("Creating new payment transaction for tx_ref: {$txRef}");
// Default to pending status
// $paymentTransaction = new PaymentTransaction();
$paymentTransaction = $this->paymentTransaction->create([
'user_id' => $metadata['user_id'],
'amount' => $amount,
'payment_gateway' => 'Flutterwave',
'order_id' => $txRef,
'payment_status' => 'pending',
'school_id' => $schoolId,
]);
// $paymentTransaction->order_id = $txRef;
// $paymentTransaction->payment_id = $data['data']['id'] ?? null;
// $paymentTransaction->payment_gateway = 'Flutterwave';
// $paymentTransaction->amount = $amount;
// $paymentTransaction->payment_status = 'pending';
// $paymentTransaction->save();
Log::info("Created new payment transaction with ID: {$paymentTransaction->id}");
} else {
// Use school_id from payment transaction if not in metadata
if (!$schoolId && $paymentTransaction->school_id) {
$schoolId = $paymentTransaction->school_id;
$metadata['school_id'] = $schoolId;
}
}
// Check if payment already processed
if ($paymentTransaction->payment_status == 'succeed') {
Log::info("Payment already processed for tx_ref: {$txRef}");
return response()->json(['status' => 'success', 'message' => 'Payment already processed']);
}
// Update transaction status based on webhook status
if ($event == 'charge.completed') {
Log::info("Updating payment status to succeed for tx_ref: {$txRef}");
DB::beginTransaction();
try {
Log::info("Payment status updated to succeed for tx_ref: {$txRef}");
// Add payment_transaction_id to metadata for processing
$metadata['payment_transaction_id'] = $paymentTransaction->id;
// Process payment based on type
\Log::info("metadata=>". $metadata['type']);
if (isset($metadata['type']) ) {
$type = $metadata['type'];
if ($type == 'addon') {
Log::info("Processing addon payment");
// Update payment status
$paymentTransaction->payment_status = 'succeed';
$paymentTransaction->save();
$this->handleAddonPayment($metadata);
} else if ($type == 'package') {
Log::info("Processing package payment");
// Update payment status
$paymentTransaction->payment_status = 'succeed';
$paymentTransaction->save();
// Extract package details
$packageId = $metadata['package_id'] ?? null;
$packageType = $metadata['package_type'] ?? 'new';
if (!$packageId) {
throw new Exception("Package ID not found in metadata");
}
// Set school_id in metadata
$metadata['school_id'] = $schoolId;
Log::info("Package details: ID={$packageId}, Type={$packageType}, SchoolID={$schoolId}");
if ($packageType == 'new') {
Log::info("Processing new package payment with metadata:", $metadata);
$this->handlePackagePayment($metadata);
}
else if ($packageType == 'upcoming') {
// Process upcoming prepaid plan
Log::info("Processing upcoming prepaid plan with metadata:", $metadata);
$this->processUpcomingPrepaidPlan($metadata);
}
else if ($packageType == 'immediate') {
// Process immediate package change
Log::info("Processing immediate package change with metadata:", $metadata);
$this->processImmediatePackageChange($metadata);
}
}
}
// Clear cache
if ($schoolId) {
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.FEATURES'), $schoolId);
}
DB::commit();
Log::info("Payment processed successfully for tx_ref: {$txRef}");
return response()->json(['status' => 'success']);
} catch (Exception $e) {
DB::rollBack();
Log::error("Error processing payment: " . $e->getMessage(), [
'trace' => $e->getTraceAsString(),
'tx_ref' => $txRef
]);
throw $e;
}
}
else if ($status == 'failed') {
$paymentTransaction->payment_status = 'failed';
$paymentTransaction->save();
Log::info("Payment status updated to failed for tx_ref: {$txRef}");
return response()->json(['status' => 'failed'], 400);
}
return response()->json(['status' => 'received'], 200);
} catch (Exception $e) {
if (DB::transactionLevel() > 0) {
DB::rollBack();
}
Log::error("Flutterwave Webhook Error: " . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
return response()->json(['error' => $e->getMessage()], 400);
}
}
public function paystack(Request $request)
{
Log::info('Paystack Webhook Called');
$webhookBody = file_get_contents('php://input');
try {
$data = json_decode($webhookBody, true);
Log::info("Paystack Webhook Data:", [$data]);
if (!$data) {
throw new Exception('Invalid webhook payload');
}
$event = $data['event'] ?? '';
$txRef = $data['data']['reference'] ?? '';
$amount = $data['data']['amount'] ?? 0;
$status = $data['data']['status'] ?? '';
$customerEmail = $data['data']['customer']['email'] ?? '';
if (empty($txRef)) {
throw new Exception('Transaction reference not found in webhook data');
}
Log::info("Processing Paystack tx_ref: {$txRef}, event: {$event}, status: {$status}");
$metadata = $data['data']['metadata'];
if (is_string($metadata)) {
$metadata = json_decode($metadata, true);
}
$schoolId = $metadata['school_id'] ?? null;
if (!$schoolId) {
Log::warning("Paystack Webhook: School ID not found in metadata");
}
DB::setDefaultConnection('mysql');
$paymentTransaction = PaymentTransaction::where('order_id', $txRef)->first();
if (!$paymentTransaction) {
Log::info("Creating new payment transaction for tx_ref: {$txRef}");
$paymentTransaction = $this->paymentTransaction->create([
'user_id' => $metadata['user_id'],
'amount' => $amount,
'payment_gateway' => 'Paystack',
'order_id' => $txRef,
'payment_status' => 'pending',
'school_id' => $schoolId,
]);
Log::info("Created new payment transaction with ID: {$paymentTransaction->id}");
} else {
if (!$schoolId && $paymentTransaction->school_id) {
$schoolId = $paymentTransaction->school_id;
$metadata['school_id'] = $schoolId;
}
}
if ($paymentTransaction->payment_status == 'succeed') {
Log::info("Payment already processed for tx_ref: {$txRef}");
return response()->json(['status' => 'success', 'message' => 'Payment already processed']);
}
if ($event == 'charge.success') {
Log::info("Updating payment status to succeed for tx_ref: {$txRef}");
DB::beginTransaction();
try {
$metadata['payment_transaction_id'] = $paymentTransaction->id;
// Process payment based on type
if (isset($metadata['type'])) {
$type = $metadata['type'];
\Log::info("Processing payment type: {$type}");
if ($type == 'addon') {
Log::info("Processing addon payment");
// Update payment status
$this->paymentTransaction->builder()->where('id', $paymentTransaction->id)->update([
'payment_status' => 'succeed',
'payment_id' => $data['data']['id'] ?? null,
]);
$this->handleAddonPayment($metadata);
} else if ($type == 'package') {
Log::info("Processing package payment");
// Update payment status
$this->paymentTransaction->builder()->where('id', $paymentTransaction->id)->update([
'payment_status' => 'succeed',
'payment_id' => $data['data']['id'] ?? null,
]);
// Extract package details
$packageId = $metadata['package_id'] ?? null;
$packageType = $metadata['package_type'] ?? 'new';
if (!$packageId) {
throw new Exception("Package ID not found in metadata");
}
// Set school_id in metadata
$metadata['school_id'] = $schoolId;
Log::info("Package details: ID={$packageId}, Type={$packageType}, SchoolID={$schoolId}");
if ($packageType == 'new') {
Log::info("Processing new package payment with metadata:", $metadata);
$this->handlePackagePayment($metadata);
} else if ($packageType == 'upcoming') {
// Process upcoming prepaid plan
Log::info("Processing upcoming prepaid plan with metadata:", $metadata);
$this->processUpcomingPrepaidPlan($metadata);
} else if ($packageType == 'immediate') {
// Process immediate package change
Log::info("Processing immediate package change with metadata:", $metadata);
$this->processImmediatePackageChange($metadata);
}
}
}
// Clear cache
if ($schoolId) {
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.FEATURES'), $schoolId);
}
DB::commit();
Log::info("Payment processed successfully for tx_ref: {$txRef}");
return response()->json(['status' => 'success']);
} catch (Exception $e) {
DB::rollBack();
Log::error("Error processing payment: " . $e->getMessage(), [
'trace' => $e->getTraceAsString(),
'tx_ref' => $txRef
]);
throw $e;
}
}
return response()->json(['status' => 'received'], 200);
} catch (Exception $e) {
Log::error("Paystack Webhook Error: " . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
return response()->json(['error' => $e->getMessage()], 400);
}
}
private function handleAddonPayment($metadata)
{
Log::info("Handling addon payment with metadata:", $metadata);
// Validate required fields and use fallbacks
$subscriptionId = $metadata['subscription_id'] ?? null;
if (empty($subscriptionId)) {
Log::warning("Addon payment: subscription_id is missing");
}
$schoolId = $metadata['school_id'] ?? null;
if (empty($schoolId)) {
Log::warning("Addon payment: school_id is missing");
return;
}
$featureId = $metadata['feature_id'] ?? null;
if (empty($featureId)) {
Log::warning("Addon payment: feature_id is missing");
}
$price = $metadata['amount'] ?? $metadata['price'] ?? 0;
// Use provided end_date or default to 30 days from now
$endDate = null;
if (!empty($metadata['end_date'])) {
$endDate = $metadata['end_date'];
} else {
$endDate = Carbon::now()->addDays(30)->format('Y-m-d');
Log::info("Using default end_date (30 days from now): {$endDate}");
}
$paymentTransactionId = $metadata['payment_transaction_id'] ?? null;
if (empty($paymentTransactionId)) {
Log::warning("Addon payment: payment_transaction_id is missing");
}
\Log::info("paymentTransactionId=>". $paymentTransactionId);
try {
$addon_data = [
'subscription_id' => $subscriptionId,
'school_id' => $schoolId,
'feature_id' => $featureId,
'price' => $price,
'start_date' => Carbon::now(),
'end_date' => $endDate,
'status' => 1,
'payment_transaction_id' => $paymentTransactionId,
];
Log::info("Creating addon subscription with data:", $addon_data);
$addonSubscription = AddonSubscription::create($addon_data);
Log::info("Addon subscription created successfully with ID: " . $addonSubscription->id);
return $addonSubscription;
} catch (Exception $e) {
Log::error("Error creating addon subscription: " . $e->getMessage(), [
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
private function handlePackagePayment($metadata)
{
Log::info("Handling package payment with metadata:", $metadata);
$new_subscription = null;
// Validate required fields
$packageId = $metadata['package_id'] ?? null;
if (empty($packageId)) {
Log::warning("Package payment: package_id is missing");
return null;
}
$schoolId = $metadata['school_id'] ?? null;
if (empty($schoolId)) {
Log::warning("Package payment: school_id is missing");
return null;
}
$paymentTransactionId = $metadata['payment_transaction_id'] ?? null;
if (empty($paymentTransactionId)) {
Log::warning("Package payment: payment_transaction_id is missing");
}
// Get package type, default to 'new' if not specified
$packageType = $metadata['package_type'] ?? 'new';
Log::info("Processing {$packageType} package subscription");
try {
DB::beginTransaction();
// New package subscription
if ($packageType == 'new') {
Log::info("Creating new subscription for package_id: {$packageId}");
$new_subscription = $this->subscriptionService->createSubscription(
$packageId,
$schoolId,
null,
1
);
// Update subscription bill with payment transaction ID
if ($new_subscription && isset($new_subscription->subscription_bill)) {
Log::info("Updating subscription bill with payment transaction ID: {$paymentTransactionId}");
SubscriptionBill::where('subscription_id', $new_subscription->id)
->update(['payment_transaction_id' => $paymentTransactionId]);
} else {
Log::warning("No subscription bill found for new subscription");
}
}
// Upcoming prepaid plan
else if ($packageType == 'upcoming') {
Log::info("Processing upcoming prepaid plan");
// Ensure all required data is present in metadata
$metadata['payment_transaction_id'] = $paymentTransactionId;
$metadata['school_id'] = $schoolId;
$metadata['package_id'] = $packageId;
$new_subscription = $this->processUpcomingPrepaidPlan($metadata);
if (!$new_subscription) {
throw new Exception("Failed to process upcoming prepaid plan");
}
}
// Immediate change current package
else if ($packageType == 'immediate') {
Log::info("Processing immediate package change");
// Ensure all required data is present in metadata
$metadata['payment_transaction_id'] = $paymentTransactionId;
$metadata['school_id'] = $schoolId;
$metadata['package_id'] = $packageId;
$new_subscription = $this->processImmediatePackageChange($metadata);
if (!$new_subscription) {
throw new Exception("Failed to process immediate package change");
}
}
// Update payment transaction ID for all subscription bills
if ($new_subscription) {
Log::info("Updating payment transaction ID for all subscription bills");
SubscriptionBill::where('subscription_id', $new_subscription->id)
->update(['payment_transaction_id' => $paymentTransactionId]);
}
DB::commit();
Log::info("Package payment processed successfully");
return $new_subscription;
} catch (Exception $e) {
DB::rollBack();
Log::error("Error in handlePackagePayment: " . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
private function processUpcomingPrepaidPlan($metadata)
{
$schoolId = $metadata['school_id'] ?? null;
$packageId = $metadata['package_id'] ?? null;
$paymentTransactionId = $metadata['payment_transaction_id'] ?? null;
$subscriptionId = $metadata['subscription_id'] ?? null;
Log::info("Starting upcoming prepaid plan processing. School ID: {$schoolId}, Package ID: {$packageId}");
if (!$schoolId || !$packageId || !$paymentTransactionId) {
Log::error("Missing required data for upcoming prepaid plan:", [
'school_id' => $schoolId,
'package_id' => $packageId,
'payment_transaction_id' => $paymentTransactionId
]);
throw new Exception("Missing required data for upcoming prepaid plan");
}
try {
// Get active plan and package
$active_plan = $this->subscriptionService->active_subscription($schoolId);
if (!$active_plan) {
Log::error("No active plan found for school ID: {$schoolId}");
return null;
}
Log::info("Current active plan found: ID={$active_plan->id}, end_date={$active_plan->end_date}");
$package = Package::find($packageId);
if (!$package) {
Log::error("Package not found with ID: {$packageId}");
return null;
}
Log::info("Package found: ID={$package->id}, Name={$package->name}, Days={$package->days}");
// Calculate start and end dates
$start_date = Carbon::parse($active_plan->end_date)->addDays()->format('Y-m-d');
$end_date = Carbon::parse($start_date)->addDays(($package->days - 1))->format('Y-m-d');
Log::info("Calculated date range: start_date={$start_date}, end_date={$end_date}");
$subscription_data = [
'package_id' => $package->id,
'name' => $package->name,
'student_charge' => $package->student_charge,
'staff_charge' => $package->staff_charge,
'start_date' => $start_date,
'end_date' => $end_date,
'package_type' => $package->type,
'no_of_students' => $package->no_of_students,
'no_of_staffs' => $package->no_of_staffs,
'billing_cycle' => $package->days,
'school_id' => $schoolId,
'charges' => $package->charges
];
Log::info("Creating subscription with data:", $subscription_data);
// Create or update subscription
$new_subscription = null;
if ($subscriptionId && $active_plan->id == $subscriptionId) {
// Same upcoming plan
Log::info("Creating new subscription for upcoming plan (same as active plan)");
$new_subscription = Subscription::create($subscription_data);
} else if ($subscriptionId) {
// Update existing record
Log::info("Updating existing subscription ID: {$subscriptionId}");
$existing = Subscription::find($subscriptionId);
if ($existing) {
$existing->update($subscription_data);
$new_subscription = $existing->fresh();
Log::info("Updated existing subscription successfully");
} else {
Log::warning("Subscription ID {$subscriptionId} not found, creating new instead");
$new_subscription = Subscription::create($subscription_data);
}
} else {
// Create new if no subscription ID
Log::info("No subscription ID provided, creating new subscription");
$new_subscription = Subscription::create($subscription_data);
}
if (!$new_subscription) {
throw new Exception("Failed to create or update subscription");
}
Log::info("Subscription created/updated successfully: ID={$new_subscription->id}");
// Add features
$subscription_features = [];
if (isset($new_subscription->package) && isset($new_subscription->package->package_feature)) {
foreach ($new_subscription->package->package_feature as $key => $feature) {
$subscription_features[] = [
'subscription_id' => $new_subscription->id,
'feature_id' => $feature->feature_id
];
}
if (count($subscription_features) > 0) {
SubscriptionFeature::upsert(
$subscription_features,
['subscription_id', 'feature_id'],
['subscription_id', 'feature_id']
);
Log::info("Added " . count($subscription_features) . " features to subscription");
}
} else {
Log::warning("No package features found for subscription ID: {$new_subscription->id}");
}
// Generate bill
$systemSettings = $this->cache->getSystemSettings();
$subscription_bill = [
'subscription_id' => $new_subscription->id,
'amount' => $new_subscription->charges,
'total_student' => 0,
'total_staff' => 0,
'due_date' => Carbon::now()->addDays($systemSettings['additional_billing_days'] ?? 7)->format('Y-m-d'),
'school_id' => $schoolId,
'payment_transaction_id' => $paymentTransactionId
];
Log::info("Creating subscription bill:", $subscription_bill);
// Create bill for active plan
$bill = SubscriptionBill::create($subscription_bill);
Log::info("Created subscription bill: ID={$bill->id}");
Log::info("Upcoming prepaid plan processing completed successfully");
return $new_subscription;
} catch (Exception $e) {
Log::error("Error in processUpcomingPrepaidPlan: " . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
private function processImmediatePackageChange($metadata)
{
$schoolId = $metadata['school_id'] ?? null;
$packageId = $metadata['package_id'] ?? null;
$paymentTransactionId = $metadata['payment_transaction_id'] ?? null;
Log::info("Starting immediate package change. School ID: {$schoolId}, Package ID: {$packageId}");
if (!$schoolId || !$packageId || !$paymentTransactionId) {
Log::error("Missing required data for immediate package change:", [
'school_id' => $schoolId,
'package_id' => $packageId,
'payment_transaction_id' => $paymentTransactionId
]);
throw new Exception("Missing required data for immediate package change");
}
try {
// Get current plan
$subscription = $this->subscriptionService->active_subscription($schoolId);
if (!$subscription) {
Log::error("No active subscription found for school ID: {$schoolId}");
return null;
}
Log::info("Current active subscription found: ID={$subscription->id}, end_date={$subscription->end_date}");
// Get package
$package = Package::find($packageId);
if (!$package) {
Log::error("Package not found with ID: {$packageId}");
return null;
}
Log::info("Package found: ID={$package->id}, Name={$package->name}, Days={$package->days}");
DB::beginTransaction();
// Postpaid plan generate bill
if ($subscription->package_type == 1) {
// Create bill for current subscription
$result = $this->subscriptionService->createSubscriptionBill($subscription, null);
Log::info("Created bill for current subscription: " . ($result ? "Success" : "Failed"));
}
// Update end date for current subscription
$oldEndDate = $subscription->end_date;
$subscription->end_date = Carbon::now()->format('Y-m-d');
$subscription->save();
Log::info("Updated end date for current subscription from {$oldEndDate} to {$subscription->end_date}");
// Delete subscription features
$featureCount = $this->subscriptionFeature->builder()->where('subscription_id', $subscription->id)->count();
$this->subscriptionFeature->builder()->where('subscription_id', $subscription->id)->delete();
Log::info("Deleted {$featureCount} features for current subscription");
// Delete upcoming subscriptions
$upcomingCount = $this->subscription->builder()
->with('package')
->doesntHave('subscription_bill')
->whereDate('start_date', '>', $subscription->end_date)
->count();
$this->subscription->builder()
->with('package')
->doesntHave('subscription_bill')
->whereDate('start_date', '>', $subscription->end_date)
->delete();
Log::info("Deleted {$upcomingCount} upcoming subscriptions");
// Update and delete addons
$addons = $this->addonSubscription->builder()->where('subscription_id', $subscription->id)->get();
$soft_delete_addon = [];
foreach ($addons as $addon) {
AddonSubscription::find($addon->id)->update(['end_date' => $subscription->end_date]);
$soft_delete_addon[] = $addon->id;
}
if (count($soft_delete_addon) > 0) {
$this->addonSubscription->builder()->whereIn('id', $soft_delete_addon)->delete();
Log::info("Updated and deleted " . count($soft_delete_addon) . " addons for current subscription");
}
// Create new subscription
$start_date = Carbon::now();
$end_date = Carbon::now()->addDays(($package->days - 1))->format('Y-m-d');
$subscription_data = [
'package_id' => $package->id,
'name' => $package->name,
'student_charge' => $package->student_charge,
'staff_charge' => $package->staff_charge,
'start_date' => $start_date,
'end_date' => $end_date,
'package_type' => $package->type,
'no_of_students' => $package->no_of_students,
'no_of_staffs' => $package->no_of_staffs,
'billing_cycle' => $package->days,
'school_id' => $schoolId,
'charges' => $package->charges
];
Log::info("Creating new subscription with data:", $subscription_data);
$new_subscription = Subscription::create($subscription_data);
Log::info("Created new subscription with ID: {$new_subscription->id}");
// Add features
$subscription_features = [];
foreach ($new_subscription->package->package_feature as $feature) {
$subscription_features[] = [
'subscription_id' => $new_subscription->id,
'feature_id' => $feature->feature_id
];
}
if (count($subscription_features) > 0) {
SubscriptionFeature::upsert(
$subscription_features,
['subscription_id', 'feature_id'],
['subscription_id', 'feature_id']
);
Log::info("Added " . count($subscription_features) . " features to new subscription");
}
// Generate bill
$systemSettings = $this->cache->getSystemSettings();
$subscription_bill = [
'subscription_id' => $new_subscription->id,
'amount' => $new_subscription->charges,
'total_student' => 0,
'total_staff' => 0,
'due_date' => Carbon::now()->addDays($systemSettings['additional_billing_days'] ?? 7)->format('Y-m-d'),
'school_id' => $schoolId,
'payment_transaction_id' => $paymentTransactionId
];
Log::info("Creating subscription bill for new subscription:", $subscription_bill);
// Create bill for new subscription
$bill = SubscriptionBill::create($subscription_bill);
Log::info("Created subscription bill for new subscription: ID={$bill->id}");
DB::commit();
Log::info("Immediate package change completed successfully");
return $new_subscription;
} catch (Exception $e) {
DB::rollBack();
Log::error("Error in processImmediatePackageChange: " . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}