endPointRPS = _env('CF_RPS_ENDPOINT', 'http://65.1.61.53:8000/api/5th_emi_date'); $this->endPointRPSSubvention = _env('CF_RPS_SUBVENTION_ENDPOINT', 'http://65.1.61.53:8000/api/subvention_rps'); $this->endPointReconciliation = _env('CF_PAYMENT_RECON', 'http://65.1.61.53:8000/api/payment_allocation_st'); $this->endPointSettlementReconciliation = _env('CF_PAYMENT_SETTLE_RECON', 'http://65.1.61.53:8000/api/settlement_reconcile'); $this->endPointForeclosureAllocation = _env('CF_FORECLOSURE_ALLOCATION', 'http://65.1.61.53:8000/api/foreclosure_allocation'); $this->endPointOverdueUpdate = _env('CF_OVERDUE_UPDATE', 'http://65.1.61.53:8000/api/update_loan_data'); $this->endPointBouncePenalUpdate = _env('CF_BOUNCE_PENAL_UPDATE', 'http://65.1.61.53:8000/api/bounce_penal_update'); $this->endPointPrePaymentRPS = _env('PRE_PAYMENT_RPS', 'http://65.1.61.53:8000/api/pre_part_payment_v2'); $this->endPointHomeVilleRPS = _env('HOME_VILLE_RPS', 'http://65.1.61.53:8000/api/homeville_rps_update'); } // Public function to gether RPS details public function getRPSDetails($app_id, $returnJson = false, $docType = '', $is_payment = false, $isHybrid = true){ try { $exceptionList = [262979,262999,263304,258629,253121,258629,270925]; $application = ConsumerApplication::with('emi', 'emi.offer')->where('id', $app_id)->whereNotIn('id', $exceptionList)->first(['id','state']); $multiStageDisbursement = new MultiStageDisbursement(); $activatedForAll = true; $isMultiTranche = false;//$multiStageDisbursement->canPartialDisbursement($application->emi->offer->id, $application); if ($activatedForAll) { //Activated for all the cases, earlier multi tranche was not considered if (strtotime(Carbon::now()->format('d-m-Y')) >= strtotime(_env('CF_RPS_START_DATE')) && isset($application->state)) { if ((in_array($application->state, ['nach registered', 'closed', 'cancelled']) || ($application->state == 'disbursed' && $docType != 'Welcome Letter') || ($application->state == 'disbursed' && $is_payment)) && !$this->is_pre_part_payment ) { (new Slack(new Request(['title' => 'Reco Logs getRPSDetails out', 'fields' => ['id' => $app_id,'type' => $docType], 'group' => Slack::$STAGE_TESTING])))->send(); $is_exist = RPS_Logs::where('application_id', $app_id)->exists(); if ($is_exist) { return RPS_Logs::where('application_id', $app_id)->get()->toArray(); } return false; } $this->application_id = $app_id; $this->setRPSPayload(); $response = $this->apiCall($isHybrid ? 'rps' : 'rps_subvention'); (new Slack(new Request(['title' => 'Reco Logs apiCall', 'fields' => ['response' => json_encode($response), 'payload' => $this->payload], 'group' => Slack::$STAGE_TESTING])))->send(); $isIn = true; if (isset($response['rps_json'])) { $rps_summary = $response; $rps_json = $response['rps_json']; unset($rps_summary['rps_json']); foreach ($rps_json as $index => $resp) { if((($this->isBPIApplicable($app_id, $isMultiTranche) || (!$this->is_bpi_applicable)) && isset($resp['bpi_interest']) && $resp['bpi_interest'] != 0) && $isIn){ $isIn = false; $isMultiTranche = false; if ($resp['bpi_interest'] > 0 && $resp['emi_amount'] > 0){ $rps_json[$index]['total_payable'] = $rps_json[$index]['total_payable'] - $rps_json[$index]['bpi_interest']; $rps_json[$index]['bpi_interest'] = 0; }else { unset($rps_json[$index]); continue; } } $rps_json[$index]['application_id'] = (int)$this->application_id ?? $app_id ?? 0; $rps_json[$index]['created_at'] = Carbon::now()->toDateTimeString(); if($index == 0){ $rps_json[$index]['loan_summary'] = json_encode($rps_summary); }else{ if ($index == 1 && !isset($rps_json[0])){ $rps_json[$index]['loan_summary'] = json_encode($rps_summary); }else { $rps_json[$index]['loan_summary'] = null; } } } $rps_json = array_values($rps_json); $is_update = RPS_Logs::where('application_id', $this->application_id)->exists(); if ($is_update) { (new Slack(new Request(['title' => 'RPS Delete 1', 'fields' => ['application_id' => $this->application_id], 'group' => Slack::$STAGE_TESTING])))->send(); RPS_Logs::where('application_id', $this->application_id)->delete(); } RPS_Logs::insert($rps_json); // if($docType == 'Welcome Letter'){ if(!$this->is_pre_part_payment) { $this->setEMIdetails($app_id, $rps_json); } // } if ($returnJson) { return $rps_json; } return true; } return false; } } return false; } catch (\Exception $e) { $cwFactory = new CloudWatchLoggerFactory(); $_data = array( 'exception' => 'Error while calling RPS API', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ); $cwFactory->logger(json_encode($_data)); } } private function isBPIApplicable($id, $isMultiTranche = false){ $exceptionApplicationIds = explode(',', _env('AATTA_CHAKKI_EXCEPTION', '')); if (in_array($id, $exceptionApplicationIds)) { return false; } if($isMultiTranche){ return true; } $application = ConsumerApplicationElement::where('application_id', $id)->where('type_of_customer_sub_category', 'Aatta Chakki')->first(['id']); return isset($application->id); } public function getRevisedRPS($data){ $this->payload = json_encode( $data ); return $this->apiCall('revised_pre_payment_rps'); } // Public function to check if present public function checkRPSExists($app_id){ return RPS_Logs::where('application_id', $app_id)->whereNull('deleted_at')->exists(); } // Public function to reset entry public function deleteRPSRecord($app_id){ (new Slack(new Request(['title' => 'RPS Delete', 'fields' => ['app_id' => $app_id], 'group' => Slack::$STAGE_TESTING])))->send(); RPS_Logs::where('application_id', $app_id)->whereNull('deleted_at')->delete(); return true; } private function setRPSPayload(){ $application_details = ConsumerApplication::with(['offer' => function ($query) { $query->select('id', 'tenure', 'advance_emi', 'interest_rate', 'annualised_irr'); }, 'emi' => function ($query) { $query->select('application_id', 'offer_id', 'tenure', 'remaining_emi', 'advance_emi', 'emi_date', 'subvention', 'processing_fee', 'annualised_interest', 'annualised_irr', 'upfront_from_customer'); }, 'proFormaInvoice' => function ($query) { $query->select('application_id', 'insurance_premium_amount', 'insurance_premium_by_merchant'); }, 'application_element' => function ($query) { $query->select('application_id', 'payment_transfer_date'); } ]) ->where('id', $this->application_id)->first(['id', 'loan_amount', 'approved_amount', 'offer_id']); $statusLog = StatusLogs::where('application_id', $this->application_id)->where('status', 'disbursed')->orderBy('created_at', 'desc')->first(['id', 'application_id', 'created_at']); $disbursal_date_str = $this->is_pre_part_payment && isset($statusLog->created_at) ? Carbon::parse($statusLog->created_at)->format('d-m-Y') : Carbon::now()->format('d-m-Y'); $loan_amount = $application_details->loan_amount; $approved_amount = $application_details->approved_amount; $insurance_fee = $application_details->proFormaInvoice->insurance_premium_amount ?? 0; $emiCalculation = new EMICalculation(new Request()); if ($emiCalculation->isLoanAmountAddedInLoanAmount($this->application_id, $application_details->pro_forma_invoice)){ $loan_amount += $insurance_fee; $approved_amount += $insurance_fee; } /* * Same treatment for the charges opted in to "added in loan amount": the EMIs * were built on a principal that includes them, so the RPS has to be built on * that principal too. The EMI engine owns the figure, so it is asked directly * rather than recomputed here - appId is what tells it which application to * read, exactly as EMICalculation::getEMIs() sets it. */ $chargesCalculation = new \App\EMICalculation(); $chargesCalculation->appId = $this->application_id; $totalCharges = $chargesCalculation->get_charges_in_loan_amount_if_applicable(); if ($totalCharges) { $loan_amount += $totalCharges; $approved_amount += $totalCharges; } $cwFactory = new CloudWatchLoggerFactory(); $cwFactory->logger(json_encode(['title' => 'Set RPS Payload', 'application_id' => $this->application_id, 'loan_amount' => $approved_amount, 'emi_date' => $application_details->emi->emi_date, 'annualised_interest' => $application_details->emi->annualised_interest, 'annualised_irr' => $application_details->offer->annualised_irr, 'upfront_from_customer' => $application_details->emi->upfront_from_customer])); $payload = [ 'application_id' => $this->application_id, 'loan_principal' => $approved_amount - $application_details->emi->upfront_from_customer ?? $loan_amount - $application_details->emi->upfront_from_customer ?? 0, 'annualised_interest' => (float)$application_details->emi->annualised_interest ?? (int)$application_details->offer->annualised_irr ?? 0, 'annualised_irr' => (float)$application_details->emi->annualised_irr ?? (int)$application_details->offer->annualised_irr ?? 0, 'remaining_emi' => $application_details->emi->remaining_emi ?? $application_details->offer->tenure-$application_details->offer->advance_emi ?? 0, 'disbursal_date_str' => $disbursal_date_str, 'first_emi_date_str' => Carbon::parse($application_details->emi->emi_date)->format('d-m-Y'), 'morat_months' => 0, 'morat_start_date_str' => '', 'subvention' => $application_details->emi->subvention ?? 0, 'insurance_amount' => $insurance_fee, 'processing_fee' => $application_details->emi->processing_fee ?? 0, ]; $this->payload = json_encode($payload); } // public function to set EMI details for the application public function setEMIdetails($app_id, $rps_json = ''){ try { if($rps_json == ''){ $rps_json = RPS_Logs::where('application_id', $app_id)->get(); } $emi_exists = EmiPayment::where('application_id', $app_id)->exists(); if($emi_exists){ EmiPayment::where('application_id', $app_id)->delete(); } (new Slack(new Request(['title' => 'Reco Logs setEMIdetails', 'fields' => ['payload' => json_encode($rps_json)], 'group' => Slack::$STAGE_TESTING])))->send(); // Build every EMI row first, then write them with bulk inserts instead of one // INSERT per EMI. Carbon::now() is resolved once so all rows of a schedule share // the same stamp, and updated_at is set explicitly because insert() bypasses // Eloquent's automatic timestamps. $now = Carbon::now()->toDateTimeString(); $emi_rows = []; foreach($rps_json as $rps){ $emi_rows[] = [ 'emi_date' => $rps->emi_date ?? $rps['emi_date'], 'pre_emi_amount' => $rps->bpi_interest ?? $rps['bpi_interest'] ?? 0, 'emi_amount' => $rps->emi_amount ?? $rps['emi_amount'] ?? 0, 'bounce_charges' => 0, 'delay_penalty' => 0, 'payment_status' => 'pending', 'application_id' => $rps->application_id ?? $rps['application_id'] ?? 0, 'due_date' => $rps->due_date ?? $rps['due_date'], 'total_payable' => $rps->total_payable ?? $rps['total_payable'] ?? 0, 'amount_due' => $rps->total_payable ?? $rps['total_payable'] ?? 0, 'charges_waived' => 0, 'created_at' => $now, 'updated_at' => $now, ]; } EmiPayment::insert($emi_rows); // Debug trail: the counts separate "RPS came back empty" from "rows got dropped", // and in_db is read back from emi_payments so a silent mismatch shows up here. (new Slack(new Request(['title' => 'Reco Logs setEMIdetails Bulk Insert done', 'fields' => ['app_id' => $app_id]]))); return true; } catch (\Exception $e){ $cwFactory = new CloudWatchLoggerFactory(); $_data = array( 'exception' => 'Error while setting EMI details after RPS API', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), 'application_id' => $app_id ); $cwFactory->logger(json_encode($_data)); return false; } } // Public function to perform Reconciliation public function reconcilePayment($app_id, $data){ $_data = array_slice($data, 0, 2); $this->application_id = $app_id; $this->payment_details = $_data; $this->setReconciliationPayload('reconciliation'); $response = $this->apiCall('reconciliation'); if($response){ $this->getReconciliationDetails($response, $data); } return $response; } public function callReconcileApi($app_id, $data){ $this->application_id = $app_id; $this->payment_details = $data; $this->setReconciliationPayload('settlement_reconcile'); $response = $this->apiCall('settlement_reconcile'); // if($response){ // $this->getReconciliationDetails($response, $data); // } return $response; } // Calls the Python foreclosure allocation service. Python is the sole writer of BOTH the // monolithic 'Foreclosure' row and the granular breakup rows, in one transaction. public function callForeclosureAllocationApi($app_id, $data){ $this->application_id = $app_id; $this->payment_details = $data; $this->setReconciliationPayload('foreclosure_allocation'); return $this->apiCall('foreclosure_allocation'); } // Builds the allocation payload from the raw payment $data (the dict passed to // updateAdvanceAmount) and triggers the Python allocation. Returns true only if Python // successfully wrote the rows; callers use the false case to fall back to a local insert. public function allocateForeclosure($application_id, $data){ $payment_date = Carbon::parse($data['payment_date'])->format('Y-m-d'); $payload = [ 'application_id' => (int)$application_id, 'amount' => (int)($data['advance_amount'] ?? 0), 'payment_date' => $payment_date, 'payment_reconciliation_date' => isset($data['payment_reconciliation_date']) ? Carbon::parse($data['payment_reconciliation_date'])->format('Y-m-d') : $payment_date, 'mode' => $data['mode'] ?? '', 'paid_by' => $data['paid_by'] ?? null, 'collected_by' => $data['collected_by'] ?? null, 'collected_by_type' => $data['collected_by_type'] ?? 1, 'wallet_name' => $data['wallet_name'] ?? '', 'trasanction_id' => $data['trasanction_id'] ?? null, 'trasanction_acknowledgement' => $data['trasanction_acknowledgement'] ?? null, 'cf_transaction_id' => $data['cf_transaction_id'] ?? null, 'bank_id' => $data['bank_id'] ?? 0, 'reason' => $data['reason'] ?? '', 'status' => $data['status'] ?? 'success', 'adjust_with_disbursement' => $data['adjust_with_disbursement'] ?? 0, ]; $response = $this->callForeclosureAllocationApi($application_id, $payload); if (isset($response['status']) && $response['status'] == 'Success') { return true; } $errorDetail = isset($response['message']) ? $response['message'] : json_encode($response); throw new \Exception("API returned: " . $errorDetail); } // Private function to get details and set the values of reconciliation in tables private function getReconciliationDetails($response, $data){ $emi_payment_date = []; Reconciliation_Logs::where('id', $this->reconciliation_id)->update([ 'response' => json_encode($response), 'updated_at' => Carbon::now()->toDateTimeString() ]); $emi_paid = 0; $emi_due = 0; $overdue_amount = 0; $today = Carbon::now()->toDateString(); $overPayment = $response['over_payment'] ?? 0; $total_amount_due = 0; if($overPayment > 0){ PaymentTransaction::insert([ 'emi_payment_id' => 0, 'application_id' => $this->application_id, 'amount' => $overPayment ?? 0, 'mode' => $data['payment_mode'], 'trasanction_id' => $data['trasanction_id'], 'trasanction_acknowledgement' => $data['trasanction_acknowledgement'], 'payment_date' => Carbon::parse($data['payment_date'])->format('Y-m-d'), 'status' => $data['status'], 'paid_by' => $data['paid_by'], 'created_at' => Carbon::now()->toDateTimeString(), 'collected_by' => $data['collected_by'], 'collected_by_type' => $data['collected_by_type'], 'payment_for' => 'Over Payment', 'payment_reconciliation_date' => Carbon::parse($data['payment_reconciliation_date'])->format('Y-m-d'), 'cf_transaction_id' => $data['cf_transaction_id'], 'bank_id' => $data['bank_id'], 'umrn' => $data['umrn'], 'wallet_name' => $data['wallet_name'], 'reason' => $data['reason'], 'adjust_with_disbursement' => $data['adjust_with_disbursement'] ]); } foreach($response['payment_allocation_json'] as $value){ $_payment_details = explode(',', $value['payment_details']); if($_payment_details[0] != '') { foreach ($_payment_details as $val) { $_payment = explode(' - ', $val); $_payment_date = $_payment[0]; $_payment_amount = $_payment[1]; $_payment_for = $_payment[2]; if($this->isBPIReceived($this->application_id, $_payment_for)) { if ($_payment_amount > 0) { PaymentTransaction::insert([ 'emi_payment_id' => $value['emi_payment_id'], 'application_id' => $this->application_id, 'amount' => $_payment_amount ?? 0, 'mode' => $data['payment_mode'], 'trasanction_id' => $data['trasanction_id'], 'trasanction_acknowledgement' => $data['trasanction_acknowledgement'], 'payment_date' => Carbon::parse($_payment_date)->format('Y-m-d'), 'status' => $data['status'], 'paid_by' => $data['paid_by'], 'created_at' => Carbon::now()->toDateTimeString(), 'collected_by' => $data['collected_by'], 'collected_by_type' => $data['collected_by_type'], 'payment_for' => $_payment_for == 'bpi' ? 'Pre-EMI' : 'EMI', 'payment_reconciliation_date' => Carbon::parse($data['payment_reconciliation_date'])->format('Y-m-d'), 'cf_transaction_id' => $data['cf_transaction_id'], 'bank_id' => $data['bank_id'], 'umrn' => $data['umrn'], 'wallet_name' => $data['wallet_name'], 'reason' => $data['reason'], 'adjust_with_disbursement' => $data['adjust_with_disbursement'] ]); } } } unset($value['payment_details']); } $emi_payment_date[$value['emi_payment_id']] = $value; $emi_payment_date[$value['emi_payment_id']]['payment_date'] = $_payment_date ?? $value['payment_date']; if($value['payment_status'] == 'success'){ $emi_paid += 1; }else{ $emi_due += 1; $_due_amount = $value['total_payable'] - $value['total_paid']; if($today > $value['due_date']){ $overdue_amount += $_due_amount; } } } $emi_payments = EmiPayment::whereIn('id', array_keys($emi_payment_date))->get(); foreach ($emi_payments as $epayment) { $epayment->bounce_charges = $emi_payment_date[$epayment->id]['bounce_charges']; $epayment->delay_penalty = $emi_payment_date[$epayment->id]['delay_penalty']; // $epayment->payment_mode = $data['payment_mode']; $epayment->charges_waived = $emi_payment_date[$epayment->id]['charges_waived']; if($emi_payment_date[$epayment->id]['payment_date'] != " "){ $epayment->payment_date = Carbon::parse($emi_payment_date[$epayment->id]['payment_date'])->toDateTimeString(); }else{ $epayment->payment_date = Carbon::parse($epayment->payment_date)->toDateTimeString(); } $epayment->payment_status = $emi_payment_date[$epayment->id]['payment_status']; $epayment->total_payable = $emi_payment_date[$epayment->id]['total_payable']; $epayment->amount_due = $emi_payment_date[$epayment->id]['amount_due']; $epayment->delay_days = $emi_payment_date[$epayment->id]['delay_days']; // $epayment->moratorium = $emi_payment_date[$epayment->id]['moratorium'] ?? 0; // $epayment->moratorium_amount = $emi_payment_date[$epayment->id]['moratorium_amount'] ?? 0; $epayment->updated_at = Carbon::now()->toDateTimeString(); $epayment->save(); } $total_amount_due = EmiPayment::where('application_id', $this->application_id)->get(['id', 'amount_due'])->sum('amount_due'); // $this->updateForeclosure($this->application_id, [ // 'emi_pending' => $emi_due, // 'emi_paid' => $emi_paid, // 'amount_over_due' => $overdue_amount, // 'total_amount_due' => $total_amount_due // ]); } public function isBPIReceived($id, $_payment_for){ if ($_payment_for == 'bpi'){ $paymentTransaction = PaymentTransaction::where('application_id', $id)->whereNull('is_deleted_at')->where('payment_for', 'Pre-EMI')->first(['id']); if (isset($paymentTransaction->id)){ return false; } } return true; } // Private function to set Payload for Reconciliation API private function setReconciliationPayload($type){ if($type == 'settlement_reconcile' || $type == 'foreclosure_allocation'){ $payload = $this->payment_details; }else{ $payload = [ 'application_id' => (int)$this->application_id, 'payment_list' => $this->payment_details ]; } $this->payload = json_encode($payload,1); $this->reconciliation_id = Reconciliation_Logs::insertGetId([ 'application_id' => $this->application_id, 'request' => json_encode($payload), 'created_at' => Carbon::now()->toDateTimeString() ]); } public function callPaymentUpdateApis($app_id){ $payload = [ 'app_id_list' => [ (int)$app_id ] ]; $this->payload = json_encode($payload,1); $this->apiCall('payment_api_overdue'); //$this->apiCall('payment_api_penal'); return true; } /*// Private function to call and set all changes after Recon API private function callReconciliationAPI(){ $response = $this->apiCall(); return json_decode($response, 1); }*/ // Generic API call for all APIS private function apiCall($type){ $curl = curl_init(); $url = $type == 'rps' ? $this->endPointRPS : $this->endPointReconciliation; if($type == 'rps_subvention') { $url = $this->endPointRPSSubvention; } if($type == 'settlement_reconcile') { $url = $this->endPointSettlementReconciliation; } if($type == 'foreclosure_allocation') { $url = $this->endPointForeclosureAllocation; } if($type == 'bounce_penal_update' || $type == 'payment_api_penal') { $url = $this->endPointBouncePenalUpdate; } if($type == 'overdue_update' || $type == 'payment_api_overdue') { $url = $this->endPointOverdueUpdate; } if($type == 'revised_pre_payment_rps'){ $url = $this->endPointPrePaymentRPS; } if ($type == 'fetch_homeville_rps'){ $url = $this->endPointHomeVilleRPS; } curl_setopt_array($curl, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $this->payload, CURLOPT_HTTPHEADER => array( 'Content-Type: application/json', ), )); $curl1 = $curl; $response = curl_exec($curl1); (new Slack(new Request(['title' => 'Recon API Response', 'fields' => [json_encode($response)]])))->send(); if (curl_error($curl1)) { $error = curl_error($curl1); (new Slack(new Request(['title' => 'Recon API Error', 'fields' => ['error' => $error, 'url' => $url]])))->send(); } curl_close($curl1); return json_decode($response, 1); } //Public function to get updated repayment status public function getRepaymentUpdates($app_id){ $data = [ 'amount' => 0, 'payment_date' => Carbon::now()->format('d-m-Y') ]; return $this->reconcilePayment($app_id, $data); } private function normalizeRPS($response){ $data = []; foreach($response['data'] as $key => $item){ if (isset($item['emi_type']) && $item['emi_type'] == 'foreclosure') { $rowId = 'Foreclosure'; } elseif ($item['interest_due'] > 0) { $rowId = 'Pre-EMI'; } else { $rowId = $key++; } $data[] = [ 'id' => $rowId, 'due_date' => $item['due_date'] ?? '', 'principal_outstanding' => $item['closing_pos'] ? round($item['closing_pos']) : 0, 'bpi_interest' => $item['pre_emi_amount'] ?? 0, 'principal_comp' => $item['principal_due'] ?? 0, 'interest_comp' => $item['interest_due'] ?? 0, 'bounce_charges' => $item['bounce_charges'] ?? 0, 'delay_penalty' => $item['delay_penalty'] ?? 0, 'emi_amount' => $item['emi_amount'] ?? 0, 'charges_waived' => $item['charges_waived'] ?? 0, 'amount_due' => $item['total_amount_due'] ?? 0, 'total_paid_for_bpi' => $item['pre_emi_paid'] ?? 0, 'payment_status' => $item[''] ?? '', 'payment_date' => $item['payment_date'] ?? $item['latest_payment_date'], 'total_paid' => $item['total_amount_paid'] ?? 0, 'emi_type' => $item['emi_type'] ?? 'emi', ]; } return $data; } //Public function to get updated details for account statement public function getStatementDetails($app_id, $history){ $consumerApplicationController = new ConsumerApplicationController(); $frontController = new FrontController(); $hashids = new Hashids('Creditfair by CS', 20); $hashed_id = $hashids->encode($app_id); if ($history) { $response['payment_allocation_json'] = $this->normalizeRPS($history); $response['new_rps_v1'] = true; $response['history'] = $history; }else{ $response = $this->getRepaymentUpdates($app_id); } /* Account statement return response */ $application = ConsumerApplication::with([ 'merchant' => function ($query) { $query->select('id', 'merchant_name'); }, 'category' => function ($query) { $query->select('id', 'name'); }, 'disbursed' => function ($query) { $query->select('application_id', 'created_at'); }, 'emi' => function ($query) { $query->select('application_id', 'tenure', 'remaining_emi','advance_emi','emi_date', 'emi', 'processing_fee', 'interest_rate', 'upfront_from_customer'); }, 'proFormaInvoice' => function ($query) { $query->select('application_id', 'loan_application_no', 'created_at'); }, 'application_element' => function ($query) { $query->select('application_id', 'product_name', 'total_amount_due', 'amount_over_due', 'emi_paid', 'emi_pending'); }, 'emi.offer' => function ($query) { $query->select('id', 'processing_fee_first_emi'); } ])->where('id', $app_id)->first(['id', 'merchant_id', 'purpose', 'name', 'lender_id', 'offer_id', 'state', 'approved_amount']); $_principal_outstanding = 0; $_principal_outstanding_paid = 0; $rev_response_json = array_reverse($response['payment_allocation_json']); $length = count($rev_response_json); $scheduleData = []; $pending_transaction = []; $success_transaction = []; $last_transaction = []; $pre_emi_transaction = []; $total_amount_paid = 0; $installment_number = 0; foreach($rev_response_json as $key => $resp){ $index = ($key + 1); $resp['emi_type'] = $resp['emi_type'] ?? 'emi'; //$_principal_outstanding_paid += $resp['principal_comp_paid']; //$_principal_outstanding_old = $_principal_outstanding; //$_principal_outstanding += $resp['principal_comp']; //$_principal_outstanding_old -= $resp['principal_comp_paid']; if($resp['bpi_interest'] > 0){ $pre_emi_transaction [] = [ 'id' => 'Pre-EMI', 'due_date' => date('d-M-Y', strtotime($resp['due_date'])), 'next_emi_word' => $frontController->getWordsFromInstallment($installment_number), 'principal_outstanding' => $resp['principal_outstanding'], 'installment_amount' => $resp['bpi_interest'], 'principal' => 0, 'interest' => $resp['bpi_interest'], 'closing_balance' => 0, 'bounce' => 0, 'penal' => 0, 'charges_waived' => $resp['emi_amount'] > 0 ? 0 : $resp['charges_waived'], 'total_due' => $resp['amount_due'], 'amount_received' => $resp['total_paid_for_bpi'], 'payment_date' => $resp['payment_status'] == 'pending' ? '' : date('d-M-Y', strtotime($resp['payment_date'])), 'last_payment_date' => '', 'payment_mode' => '', 'moratorium_amount' => 0, 'payment_status' => $resp['total_paid_for_bpi'] == $resp['bpi_interest'] ? 'success' : $resp['payment_status'], 'emi_type' => $resp['emi_type'] ]; $total_amount_paid += $resp['total_paid_for_bpi']; } if($resp['emi_amount'] > 0 || $resp['emi_type'] == 'prepartpayment') { $installment_number++; $_schedule_var = [ 'id' => $installment_number, 'due_date' => date('d-M-Y', strtotime($resp['due_date'])), 'next_emi_word' => $frontController->getWordsFromInstallment($installment_number), 'principal_outstanding' => $resp['principal_outstanding'], 'installment_amount' => $resp['emi_amount'], 'principal' => $resp['principal_comp'], 'interest' => $resp['interest_comp'], 'closing_balance' => 0, 'bounce' => $resp['bounce_charges'], 'penal' => $resp['delay_penalty'], 'charges_waived' => $resp['charges_waived'], 'total_due' => $resp['amount_due'], 'amount_received' => $resp['total_paid'], 'payment_date' => $resp['payment_status'] == 'pending' ? '' : date('d-M-Y', strtotime($resp['payment_date'])), 'last_payment_date' => '', 'payment_mode' => '', 'moratorium_amount' => 0, 'payment_status' => $resp['payment_status'], 'emi_type' => $resp['emi_type'] ]; $scheduleData[$length - $index] = $_schedule_var; $total_amount_paid += $resp['total_paid']; if ($resp['payment_status'] == 'pending') { $pending_transaction[] = $_schedule_var; } else { $success_transaction[] = $_schedule_var; } } elseif ($resp['emi_type'] == 'foreclosure') { $foreclosureScheduleRow = [ 'id' => 'Foreclosure', 'due_date' => '-', 'next_emi_word' => '', 'principal_outstanding' => '-', 'installment_amount' => '-', 'principal' => $resp['principal_comp'], 'interest' => $resp['interest_comp'], 'closing_balance' => '-', 'bounce' => '-', 'penal' => '-', 'charges_waived' => '-', 'total_due' => '-', 'amount_received' => $resp['total_paid'], 'payment_date' => date('d-M-Y', strtotime($resp['payment_date'])), 'last_payment_date' => '', 'payment_mode' => '', 'moratorium_amount' => '-', 'payment_status' => 'success', 'emi_type' => 'foreclosure' ]; $total_amount_paid += $resp['total_paid']; $success_transaction[] = $foreclosureScheduleRow; } } $scheduleData = array_reverse($scheduleData); // Append foreclosure row after reversal so it always appears last if (isset($foreclosureScheduleRow)) { $scheduleData[] = $foreclosureScheduleRow; } $pending_transaction = array_reverse($pending_transaction); $success_transaction = array_reverse($success_transaction); $last_transaction = end($success_transaction); $disbursed_date = $consumerApplicationController->getDisbursedDate($application->id); if($disbursed_date) { $disbursed_date = date('d M Y', strtotime($disbursed_date)); } // $pre_part_payment_amount = PaymentTransaction::where('application_id', $app_id)->whereNull('is_deleted_at')->where('payment_for', 'pre_part_payment')->sum('amount'); // if($pre_part_payment_amount){ // $total_amount_paid += $pre_part_payment_amount; // } $isForeclosed = 0; $advance_amount = 0; $processing_fee = 0; $processing_fee_due = (isset($application->emi->processing_fee)) ? ($application->emi->processing_fee - $processing_fee) : 0; $advance_amount_due = (isset($application->proFormaInvoice->merchant_collected_advance_amount) && $application->proFormaInvoice->merchant_collected_advance_amount && $application->emi) ? ($application->emi->upfront_from_customer - $advance_amount) : 0; $isPFReceived = false; $overPayments = PaymentTransaction::with('refundPayment')->where('application_id', $app_id)->whereIn('payment_for', ['processing_fee', 'Advance Payment', 'Foreclosure', 'foreclosure_bpi', 'foreclosure_interest', 'foreclosure_principal', 'foreclosure_bounce', 'foreclosure_penal', 'foreclosure_over_payment'])->whereNull('deleted_at')->whereNull('is_deleted_at')->whereNull('refunded_at')->get(); if ($overPayments) { foreach ($overPayments as $overpayment) { $isGranular = strpos($overpayment->payment_for, 'foreclosure_') === 0; if ($overpayment->amount > 0 && ($overpayment->payment_for == "processing_fee")) { $processing_fee = $processing_fee + $overpayment->amount; } if ($overpayment->amount > 0 && ($overpayment->payment_for == "Foreclosure" || $isGranular) && $overpayment->status != "rejected") { $isForeclosed = 1; } if($overpayment->amount > 0 && $overpayment->status != "rejected" && $overpayment->mode != 'adjusted in disbursement'){ $total_amount_paid += $overpayment->amount; $isPFReceived = true; } } } $foreclosure_breakup = $this->getNewForeclosureBreakup($app_id, $response, $processing_fee_due, $isForeclosed, $advance_amount_due); $amount_financed = EMICalculation::getLoanAmountWithAllCharges($app_id); $amount_pending = $application->application_element->total_amount_due ?? 0; if (!$isPFReceived && $application && $application->emi && $application->emi->processing_fee > 0) { if (isset($application->proFormaInvoice->processing_fee_collection_method)) { if ($application->proFormaInvoice->processing_fee_collection_method == 0) { $proforma_pf = 0; } else if ($application->proFormaInvoice->processing_fee_collection_method == 1) { $proforma_pf = 1; } } $is_collected_by_merchant = $proforma_pf ?? ((isset($application->merchant->is_collected) && $application->merchant->is_collected == 1) ? $application->merchant->is_collected : 0); if($is_collected_by_merchant == 0){ $amount_pending += $application->emi->processing_fee; } } $data = [ 'id' => $hashed_id, 'lender_id' => $application->lender_id, 'merchant_id' => $application->merchant_id, 'category_id' => $application->purpose, 'customer_name' => ucwords($application->name), 'merchant_name' => ucwords($application->merchant->merchant_name), 'amount_pending' => $amount_pending, 'amount_paid' => $total_amount_paid, 'emi_paid' => $application->application_element->emi_paid ?? 0, 'category_name' => $application->category->name ?? '', 'disbursed_date' => $disbursed_date ?? $application->disbursed->created_at ?? '', 'emi_due' => $application->application_element->emi_pending ?? 0, 'amount_due' => $application->application_element->amount_over_due ?? 0, 'advance_amount' => $application->emi->upfront_from_customer, 'total_emi' => $application->emi->tenure, 'advance_emi' => ($application->emi->tenure - $application->emi->remaining_emi), 'product' => isset($application->application_element) && !empty($application->application_element->product_name) ? ucwords($application->application_element->product_name) : '', 'agreement_no' => $application->proFormaInvoice && $application->proFormaInvoice->loan_application_no ? $application->proFormaInvoice->loan_application_no : '', 'agreement_date' => date('d-m-Y', strtotime($application->proFormaInvoice->created_at)), 'frequency' => 'Monthly', 'amount_financed' => $amount_financed, 'interest' => $application->emi->interest_rate, 'tenure' => $application->emi->tenure, 'processing_fee' => $application->emi->processing_fee, 'processing_fee_first_emi' => $application->emi->offer->processing_fee_first_emi ?? 0, 'remaining_emi' => $application->application_element->emi_pending ?? 0, 'status' => (in_array($application->state, ['disbursed', 'nach registered'])) ? 'Active' : 'Inactive', 'schedule' => array_merge($pre_emi_transaction,$scheduleData), 'last_transaction' => $last_transaction, 'pending_transaction' => $pending_transaction, 'success_transaction' => $success_transaction, 'fore_closure_amount' => $foreclosure_breakup['total_foreclosure_amount'], 'account_statement' => asset('/consumer/account-statement/'.$app_id), 'isMoratorium' => false, //TODO: 'isForeclosed' => (bool)($application->foreclosure_event->id ?? false) ]; if(isset($application->foreclosure_event->id)){ $data['foreclosedAmount'] = $application->foreclosure_event->amount; $data['foreclosedMode'] = $application->foreclosure_event->mode; $data['foreclosedDate'] = $application->foreclosure_event->payment_date ?? $application->application_element->foreclosure_date; } $pre_part_payments = PaymentTransaction::where('application_id', $app_id)->where('payment_for', 'pre_part_payment')->whereNull('is_deleted_at')->get(['id','amount', 'mode', 'payment_date']); foreach($pre_part_payments as $pre_part_payment) { if (isset($pre_part_payment->id)) { $data['prePartPaymentAmount'] = $pre_part_payment->amount; $data['prePartPaymentMode'] = $pre_part_payment->mode; $data['prePartPaymentDate'] = date('Y-m-d', strtotime($pre_part_payment->payment_date)); $data['isPrePartPayment'] = 1; } } return $data; } //Public function to get updated details for Loan Schedule Page public function getLoanScheduleDetails($app_id){ $response = $this->getRepaymentUpdates($app_id)['payment_allocation_json']; $_total_due = 0; $_principal_outstanding = 0; $_principal_outstanding_paid = 0; $principal_outstanding = []; $rev_response_json = array_reverse($response); $length = count($rev_response_json); $emi_payment_ids = []; $amount_due = []; foreach($rev_response_json as $key => $resp) { // $_principal_outstanding_paid += $resp['principal_comp_paid']; // $_principal_outstanding_old = $_principal_outstanding; // $_principal_outstanding += $resp['principal_comp']; $_total_due += $resp['payment_status'] != 'success' ? $resp['amount_due'] : 0; $emi_payment_ids[$length-$key] = $resp['emi_payment_id']; $amount_due[$length-$key] = $_total_due; // $principal_outstanding[$length-$key] = $_principal_outstanding; } // $principal_outstanding = array_reverse($principal_outstanding); $amount_due = array_reverse($amount_due); $emi_payment_ids = array_reverse($emi_payment_ids); $transactions = EmiPayment::with('transaction')->whereIn('id', $emi_payment_ids)->get()->toArray(); foreach($response as $i => $item){ $response[$i]['id'] = $item['emi_payment_id']; // $response[$i]['principal_outstanding'] = $principal_outstanding[$i]; $response[$i]['foreclosure_amount'] = $item['payment_status'] != 'success' && isset($response[$i+1]['interest_comp']) ? $item['principal_outstanding'] + $response[$i+1]['interest_comp'] + $item['amount_due'] : $item['principal_outstanding']; $response[$i]['transaction'] = $transactions[$i]['transaction'] ?? []; unset($response[$i]['emi_payment_id']); } return $response; } //Public function to get updated details for Payment Page public function getPaymentPageDetails($app_id){ try { $pre_paid_ids = explode(',', _env('PRE_PAID_CASE_IDS', '0,0')); $response = !in_array($app_id, $pre_paid_ids) ? $this->getRepaymentUpdates($app_id) : false; $application = ConsumerApplication::with([ 'emi' => function ($query) { $query->select('application_id', 'tenure', 'remaining_emi', 'advance_emi', 'emi_date', 'emi', 'processing_fee', 'upfront_from_customer'); }, 'proFormaInvoice' => function ($query) { $query->select('application_id', 'loan_application_no', 'merchant_collected_advance_amount'); }, 'application_element' => function ($query) { $query->select('id','application_id', 'product_name', 'total_amount_due', 'amount_over_due', 'emi_paid', 'emi_pending', 'foreclosure_date'); }, 'lender' => function ($query) { $query->select('id', 'name'); }, 'emi_payment' => function ($query) { $query->select('id', 'emi_date', 'payment_date', 'payment_status', 'application_id'); }, 'emi_payment.transaction' => function ($query) { $query->select('application_id', 'id', 'emi_payment_id', 'cf_transaction_id', 'amount', 'trasanction_id', 'payment_date', 'payment_reconciliation_date', 'mode', 'paid_by', 'trasanction_acknowledgement', 'refunded_at', 'bank_id'); }, 'emi_payment.transaction.bank' => function ($query) { $query->select('id', 'bank_name'); }, 'emi_payment.moratoriums' => function ($query) { $query->select('id', 'emi_date', 'application_id', 'interest_rate', 'started_date', 'stopped_date'); }, 'settlement_payment' ])->where('id', $app_id)->first(['id', 'name', 'lender_id', 'state', 'secondary_status']); $foreclosure_date = ''; $over_payment = 0; $over_payment_amount = 0; $adhoc_payment = 0; $advance_amount = 0; $processing_fee = 0; $foreclosure_amount_cals = 0; $foreclosed_info = []; $settled_info = []; $isForeclosed = 0; if (isset($application->foreclosure_event->id)) { $isForeclosed = 1; $foreclosure_date = $application->foreclosure_event->payment_date; $foreclosed_info = [ 'foreclosed_amount' => $foreclosure_amount_cals + $application->foreclosure_event->amount, 'foreclosed_payment_date' => $application->foreclosure_event->payment_date, 'foreclosed_payment_mode' => $application->foreclosure_event->mode, 'foreclosed_transaction_id' => $application->foreclosure_event->trasanction_id, 'foreclosed_cf_transaction_id' => $application->foreclosure_event->cf_transaction_id, ]; } $isSettled = false; $settlement_amount = 0; if(isset($application->secondary_status) && $application->secondary_status == 'settled'){ $settlement_amount = $application->settlement_payment->sum('amount'); $isSettled = true; } // When granular foreclosure splits exist use them (they exclude processing_fee, which is // credited separately) instead of the 'Foreclosure' monolith, so PF isn't counted twice in // amount_paid. Historical foreclosures (monolith only) keep using 'Foreclosure'. $hasForeclosureBreakup = self::hasForeclosureBreakup($app_id); $foreclosurePaidLabels = $hasForeclosureBreakup ? ['foreclosure_bpi', 'foreclosure_interest', 'foreclosure_principal', 'foreclosure_bounce', 'foreclosure_penal', 'foreclosure_over_payment'] : ['Foreclosure']; $amount_paid = PaymentTransaction::where('application_id', $app_id)->whereIn('payment_for', array_merge(['EMI', 'Pre-EMI', 'Advance Payment', 'Over Payment'], $foreclosurePaidLabels))->whereNull('is_deleted_at')->get(['id', 'amount'])->sum('amount'); $foreclosure_breakup = $application->foreclosure_breakup; $application_details = $application; unset($application->foreclosure_breakup); unset($application_details->emi_payment); $waived_off_data = (new LoanManagement())->getWaivedOff($app_id); $can_waive_off = (new AccountingController())->canWaiveOff($app_id) ? 1 : 0; $overPayments = PaymentTransaction::with('refundPayment')->where('application_id', $app_id)->whereIn('payment_for', ['processing_fee', 'Over Payment', 'Advance Payment', 'rejected', 'Foreclosure', 'refund', 'collection_charges', 'insurance_fee', 'adhoc_transaction', 'advance_amount', 'cancellation_charges', 'Write-off', 'legal_charges', 'pre_payment', 'pre_part_payment', 'settled_principal', 'settled_interest', 'settled_bounce', 'settled_penal', 'written_off_principal','written_off_interest','written_off_bounce','written_off_penal', 'foreclosure_bpi', 'foreclosure_interest', 'foreclosure_principal', 'foreclosure_bounce', 'foreclosure_penal', 'foreclosure_over_payment', 'security_deposit'])->whereNull('is_deleted_at')->get(); // Keep foreclosure_bounce / foreclosure_penal visible in the adhoc-transactions list, but hide // the other foreclosure splits (bpi/interest/principal/over_payment) and the monolith breakup; // the foreclosure lump is surfaced via the foreclosed_info card. $adhoc_transactions = $overPayments->reject(function($t){ return strpos($t->payment_for, 'foreclosure_') !== false && !in_array($t->payment_for, ['foreclosure_bounce', 'foreclosure_penal'], true); })->values(); if ($overPayments) { foreach ($overPayments as $overpayment) { if ($overpayment->amount > 0 && ($overpayment->payment_for == "Over Payment") && $overpayment->status != "rejected") { $over_payment = $over_payment + $overpayment->amount; if ($overpayment->refunded_at && $overpayment->refunded_by && $overpayment->refund_maker_id && $overpayment->refund_maker_at) { $over_payment = $over_payment - $overpayment->amount; } } if ($overpayment->amount > 0 && ($overpayment->payment_for == "processing_fee")) { $processing_fee = $processing_fee + $overpayment->amount; } if ($overpayment->amount > 0 && $overpayment->payment_for == "refund") { $over_payment = $over_payment - $overpayment->amount; } $isForeclosureLump = $hasForeclosureBreakup ? (strpos($overpayment->payment_for, 'foreclosure_') !== false) : ($overpayment->payment_for == "Foreclosure"); if ($overpayment->amount > 0 && ($overpayment->payment_for == "Over Payment" || $overpayment->payment_for == "Advance Payment" || $isForeclosureLump) && $overpayment->status != "rejected") { $adhoc_payment = $adhoc_payment + $overpayment->amount; } if ($overpayment->amount > 0 && ($overpayment->payment_for == "advance_amount")) { $advance_amount = $advance_amount + $overpayment->amount; } if ($isSettled && ($overpayment->payment_for == "settled_principal")){ $settled_info = [ 'settled_amount' => $settlement_amount, 'settled_payment_date' => $overpayment->payment_date, // 'settled_payment_reconcile_date' => $overpayment->payment_reconciliation_date, 'settled_payment_mode' => $overpayment->mode, 'settled_transaction_id' => $overpayment->trasanction_id, 'settled_cf_transaction_id' => $overpayment->cf_transaction_id, ]; } } $over_payment_amount = $over_payment; } else { $over_payment_amount = 0; } $processing_fee_due = (isset($application->emi->processing_fee)) ? ($application->emi->processing_fee - $processing_fee) : 0; $advance_amount_due = (isset($application->proFormaInvoice->merchant_collected_advance_amount) && $application->proFormaInvoice->merchant_collected_advance_amount && $application->emi) ? ($application->emi->upfront_from_customer - $advance_amount) : 0; $max_payable = $application->application_element->total_amount_due > 0 ? $application->application_element->amount_over_due : 0; $payment_logs = PaymentLogs::where('application_id', $app_id)->orderBy('created_at', 'desc')->get(); $_payment_logs = []; foreach ($payment_logs as $payment_log) { $_payment_logs[$app_id][$payment_log->emi_payment_id][] = $payment_log; } //$foreclosure_breakup = []; $foreclosure_breakup = !in_array($app_id, $pre_paid_ids) ? $this->getNewForeclosureBreakup($app_id, $response, $processing_fee_due, $isForeclosed, $advance_amount_due) : [ 'is_foreclosed' => 1, 'principal_due' => 0, 'interest_due' => 0, 'moratorium_due' => 0, 'bounce_charges' => 0, 'penal_charges' => 0, 'emi_due' => 0, 'pf_due' => 0, 'advance_due' => 0, 'total_foreclosure_amount' => 0, ]; $foreclosedAmount = DB::select("select sum(amount) as 'amount' from payment_transactions_temp where trasanction_id=(select trasanction_id from payment_transactions_temp where application_id=" . $app_id . " and payment_for='Foreclosure');"); if (isset($foreclosedAmount[0]->amount) && isset($data['foreclosure_amount']) && $foreclosedAmount[0]->amount < $data['foreclosure_amount']) { $isForeclosed = 0; } if ($foreclosure_date == "") { $foreclosure_date = date('Y-m-d', (strtotime('29 day', time()))); } $emi_payment_amount = EmiPayment::where('application_id', $app_id)->where('payment_status', 'pending')->orderBy('emi_date', 'asc')->first(['emi_amount']); return [ "paymentsData" => $application->emi_payment, "application" => $application_details, "over_payment" => $over_payment_amount, "adhoc_transactions" => $adhoc_transactions, "amount_paid" => $amount_paid, "max_payable" => $max_payable, "moratoriumsLogs" => Moratorium::with('user', 'stopped')->where('application_id', $app_id)->get(), "frequencyDetails" => ApplicationEMICalculation::where('application_id', $app_id)->first(['payment_frequency'])->toArray(), "processing_fee_due" => $processing_fee_due, "advance_amount_due" => $advance_amount_due, "foreclosure_date" => $foreclosure_date, "waived_off_data" => $waived_off_data ?? [], "can_waive_off" => $can_waive_off, "foreclosure_breakup" => $foreclosure_breakup, "payment_logs" => $_payment_logs, "foreclosed_info" => $foreclosed_info, "isForeclosed" => $isForeclosed, "isSettled" => $isSettled, "settlement_amount" => $settlement_amount, "settled_info" => $settled_info ?? [], "reference" => AvanseCreateFinance::where('application_id', $app_id)->first(), "lender_name" => $application->lender->name ?? '', "current_emi_amount" => $emi_payment_amount->emi_amount ?? 0 ]; } catch (\Exception $e) { $cwFactory = new CloudWatchLoggerFactory(); $_data = array( 'exception' => 'Error while calling Payment Page API', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ); $cwFactory->logger(json_encode($_data)); } return false; } public function updateForeclosure($application, $update = []){ $application_id = $application->id ?? $application; if (!isset($application->id)){ $application = ConsumerApplication::with(['application_element' => function ($query) { $query->select('id','application_id', 'product_name', 'total_amount_due', 'amount_over_due', 'emi_paid', 'emi_pending', 'foreclosure_date'); }])->where('id', $application_id)->first(['id']); } $foreclosure_amount = PaymentTransaction::where('application_id', $application_id)->where('payment_for', 'Foreclosure')->whereNull('is_deleted_at')->whereNull('deleted_by')->first(['amount']); $writeOff_amount = PaymentTransaction::where('application_id', $application_id)->where('payment_for', 'Write-off')->whereNull('is_deleted_at')->whereNull('deleted_by')->first(['id']); if ((isset($application->application_element->foreclosure_date) && !empty($application->application_element->foreclosure_date) && $application->application_element->foreclosure_date != "1970-01-01")) { if (isset($application->application_element) && $application->application_element) { $application_element = ConsumerApplicationElement::find($application->application_element->id); } else { $application_element = new ConsumerApplicationElement(); $application_element->application_id = $application->id; } if (($application->application_element->foreclosure_date != '' && isset($foreclosure_amount->amount)) || isset($writeOff_amount->id)) { $total_principal_due = $total_over_due = $outstanding_principal_due = $outstanding_over_due = 0; $application_element->principal_due = $total_principal_due; $application_element->amount_over_due = $total_over_due; $application_element->total_principal_due = $outstanding_principal_due; $application_element->total_amount_due = $outstanding_over_due; if (isset($update['emi_pending'])){ $application_element->emi_pending = $update['emi_pending']; } if (isset($update['emi_paid'])) { $application_element->emi_paid = $update['emi_paid']; } $application_element->save(); } }else if($update){ ConsumerApplicationElement::where('application_id', $application_id)->update($update); } } //Public function to get updated payment details public function getPaymentCalculations($app_id){ try { $pre_paid_ids = explode(',', _env('PRE_PAID_CASE_IDS', '0,0')); return !in_array($app_id, $pre_paid_ids) ? $this->getRepaymentUpdates($app_id) : false; } catch (\Exception $e) { $cwFactory = new CloudWatchLoggerFactory(); $_data = array( 'exception' => 'Error while calling Payment Calculation API', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ); $cwFactory->logger(json_encode($_data)); } return false; } //Private function to get updated foreclosure breakup details private function getNewForeclosureBreakup($id, $response, $processing_fee_due, $isForeclosed, $advance_amount_due){ try{ $breakupLogs = ForeclosureBreakup::where('application_id', $id)->orderBy('created_at', 'desc')->first(); if(isset($breakupLogs->id) && $isForeclosed){ return [ 'is_foreclosed' => 1, 'principal_due' => round($breakupLogs->principal_due, 2), 'interest_due' => round($breakupLogs->interest_due), 'moratorium_due' => round($breakupLogs->moratorium_due, 2), 'bounce_charges' => $breakupLogs->bounce_due, 'penal_charges' => $breakupLogs->penal_due, 'emi_due' => $breakupLogs->emi_due, 'pf_due' => $breakupLogs->pf_due, 'advance_due' => $breakupLogs->advance_due, 'total_foreclosure_amount' => $breakupLogs->foreclosure_amount, ]; } else{ if ($foreclosure_details = $this->getForeclosureThroughAPI($id, $response)){ return $foreclosure_details; } $pf_due = $processing_fee_due ?? 0; $emi_due = 0; $advance_due = $advance_amount_due ?? 0; $_principalDue = 0; $_interestDue = 0; $_waivedAmount = 0; $_moratInterestDue = 0; $_bounceDue = 0; $_penalDue = 0; $_bpi_interest = 0; foreach($response['payment_allocation_json'] as $resp){ if($resp['payment_status'] != 'success'){ $_principalDue += ($resp['principal_comp'] - $resp['principal_comp_paid']); $_waivedAmount += $resp['charges_waived']; if(strtotime($resp['emi_date']) <= strtotime(Carbon::now()->addMonth()->toDateTimeString())){ $_interestDue += ($resp['interest_comp'] - $resp['interest_comp_paid']); } $_moratInterestDue += $resp['morat_interest']; $_bounceDue += $resp['bounce_charges']; $_penalDue += $resp['delay_penalty']; $_bpi_interest += $resp['bpi_interest']; } $emi_due += ($resp['total_payable'] - $resp['amount_due']); } $foreclosure_amount = round($_principalDue - $_waivedAmount) + round($_moratInterestDue) + round($_interestDue) + round($_bounceDue) + round($_penalDue) + $pf_due + $advance_due+ $_bpi_interest; return [ 'is_foreclosed' => 0, 'pre_emi_due' => $_bpi_interest, 'principal_due' => round($_principalDue - $_waivedAmount, 2), 'moratorium_due' => round($_moratInterestDue, 2), 'interest_due' => round($_interestDue), 'bounce_charges' => $_bounceDue, 'penal_charges' => $_penalDue, 'emi_due' => $emi_due, 'pf_due' => $pf_due, 'advance_due' => $advance_amount_due, 'total_foreclosure_amount' => round($foreclosure_amount), ]; } } catch (\Exception $e) { $cwFactory = new CloudWatchLoggerFactory(); $_data = array( 'exception' => 'Error while calling Foreclosure Breakup Function', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ); $cwFactory->logger(json_encode($_data)); } return [ 'is_foreclosed' => 0, 'principal_due' => 0, 'moratorium_due' => 0, 'interest_due' => 0, 'bounce_charges' => 0, 'penal_charges' => 0, 'emi_due' => 0, 'pf_due' => 0, 'advance_due' => 0, 'total_foreclosure_amount' => 0, ]; } public function getForeclosureThroughAPI($id, $response = false){ if (!$response || !isset($response['new_rps_v1'])){ $response = (new AccountingController())->getLoanScheduleUsingPythonService($id); }else{ $response = $response['history']; } $total_foreclosure_amount = $response['message']['Pre-EMI due'] + $response['message']['Principal due'] + $response['message']['Moratorium due'] + $response['message']['Interest due'] + $response['message']['Bounce charges due'] + $response['message']['Penal charges due'] + $response['message']['PF due'] + $response['message']['Advance due']; return [ 'is_foreclosed' => 0, 'pre_emi_due' => round($response['message']['Pre-EMI due']), 'principal_due' => round($response['message']['Principal due']), 'moratorium_due' => round($response['message']['Moratorium due']), 'interest_due' => round($response['message']['Interest due']), 'bounce_charges' => round($response['message']['Bounce charges due']), 'penal_charges' => round($response['message']['Penal charges due']), 'emi_due' => round($response['message']['Principal due'] + $response['message']['Interest due']), 'pf_due' => round($response['message']['PF due']), 'advance_due' => round($response['message']['Advance due']), 'total_foreclosure_amount' => round($total_foreclosure_amount), ]; } public static function isNEWRPSApplicable($id){ return true; $statusLog = StatusLogs::where('application_id', $id)->where('status', 'approved')->first(['id', 'created_at']); if (isset($statusLog->id)){ return $statusLog->created_at > _env('PYTHON_SUBVENTION_CASE_APPLY_DATE'); } return false; } public function checkIfRPSExists($id){ $is_update = RPS_Logs::where('application_id', $id)->exists(); if(!$is_update) { $application = ConsumerApplication::with('activeOffer')->where('id', $id)->first(['id', 'offer_id']); $isHybrid = $application->activeOffer->subvention_type_id > 1; $this->is_pre_part_payment = true; $this->is_bpi_applicable = false; //OLD cases where RPS is not exists remove BPI from API $this->getRPSDetails($id, true, '', false, $isHybrid); $this->is_pre_part_payment = false; $this->is_bpi_applicable = true; } } public function update_bounce_penal($id){ $this->application_id = $id; $this->payload = json_encode(['app_id_list' => [(int)$this->application_id]]); return $this->apiCall('bounce_penal_update'); } public function update_over_due($id){ $this->application_id = $id; $this->payload = json_encode(['app_id_list' => [(int)$this->application_id]]); return $this->apiCall('overdue_update'); } public function fetchHomeVilleRPS($id){ $this->application_id = $id; $this->payload = json_encode(['app_id_list' => [(int)$this->application_id]]); return $this->apiCall('fetch_homeville_rps'); } public function checkSettlementEntry($applicationID) { $settlementEntry = DB::selectOne("select id from payment_transactions where payment_for = 'settled_principal' and deleted_at is null and application_id = ?", [$applicationID]); return empty($settlementEntry); } // True when granular foreclosure breakup rows exist for this application (new cases). // Readers should prefer the foreclosure_* splits over the monolithic 'Foreclosure' row when // this returns true; historical cases (monolith only) return false and keep the current flow. public static function hasForeclosureBreakup($application_id) { return PaymentTransaction::where('application_id', $application_id) ->whereIn('payment_for', ['foreclosure_bpi', 'foreclosure_interest', 'foreclosure_principal', 'foreclosure_bounce', 'foreclosure_penal', 'foreclosure_over_payment']) ->whereNull('deleted_at') ->whereNull('is_deleted_at') ->whereNull('refunded_at') ->exists(); } }