916 lines
39 KiB
PHP
916 lines
39 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries\DigitMotor;
|
|
|
|
use App\Models\MotorKycModel;
|
|
use App\Models\MotorApiLogModel;
|
|
use App\Models\MotorPaymentModel;
|
|
use App\Models\MotorPolicyModel;
|
|
use App\Models\MotorQuoteModel;
|
|
use App\Models\MotorVehicleModel;
|
|
use Config\DigitMotor as DigitMotorConfig;
|
|
|
|
/**
|
|
* Business actions against Digit OneAPI executor services.
|
|
*/
|
|
class DigitExecutorService
|
|
{
|
|
protected DigitMotorConfig $config;
|
|
protected DigitApiClient $api;
|
|
protected MotorQuoteModel $quoteModel;
|
|
protected MotorVehicleModel $vehicleModel;
|
|
protected MotorKycModel $kycModel;
|
|
protected MotorPaymentModel $paymentModel;
|
|
protected MotorPolicyModel $policyModel;
|
|
|
|
public function __construct(?DigitMotorConfig $config = null, ?DigitApiClient $api = null)
|
|
{
|
|
$this->config = $config ?? config('DigitMotor');
|
|
$this->api = $api ?? new DigitApiClient($this->config);
|
|
$this->quoteModel = new MotorQuoteModel();
|
|
$this->vehicleModel = new MotorVehicleModel();
|
|
$this->kycModel = new MotorKycModel();
|
|
$this->paymentModel = new MotorPaymentModel();
|
|
$this->policyModel = new MotorPolicyModel();
|
|
}
|
|
|
|
/**
|
|
* Create/update local quote + call motorQuickQuote.
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function quickQuote(array $input): array
|
|
{
|
|
$quoteIdIn = (int) ($input['quote_id'] ?? 0);
|
|
$existing = $quoteIdIn > 0 ? $this->quoteModel->find($quoteIdIn) : null;
|
|
|
|
// Prefer existing enquiry when updating; only mint a new one for brand-new journeys.
|
|
$enquiryId = null;
|
|
if ($existing && !empty($existing['enquiry_id'])) {
|
|
$enquiryId = (string) $existing['enquiry_id'];
|
|
} elseif (!empty($input['enquiry_id']) && $input['enquiry_id'] !== '— new —') {
|
|
$enquiryId = (string) $input['enquiry_id'];
|
|
if (!$existing) {
|
|
$existing = $this->quoteModel->where('enquiry_id', $enquiryId)->first();
|
|
}
|
|
}
|
|
if ($enquiryId === null || $enquiryId === '') {
|
|
$enquiryId = 'NH' . date('ymdHis') . random_int(100, 999);
|
|
}
|
|
|
|
$input['quote_id'] = $existing ? (int) $existing['id'] : $quoteIdIn;
|
|
$input['enquiry_id'] = $enquiryId;
|
|
|
|
$payload = $this->buildQuickQuotePayload($input, $enquiryId);
|
|
$quoteId = $this->persistQuoteShell($input, $enquiryId, 'DRAFT');
|
|
|
|
try {
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['quickQuote'],
|
|
$quoteId
|
|
);
|
|
} catch (DigitApiException $e) {
|
|
// Keep shell continuity on Digit failures so retries do not spawn duplicates.
|
|
throw new DigitApiException(
|
|
$e->getMessage(),
|
|
$e->getDigitCode(),
|
|
$e->getHttpStatus(),
|
|
[
|
|
'_nhance_shell' => [
|
|
'quote_id' => $quoteId,
|
|
'enquiry_id' => $enquiryId,
|
|
],
|
|
'digit' => $e->getResponseBody(),
|
|
],
|
|
$e,
|
|
$e->getRequestUrl(),
|
|
$e->getRequestBody()
|
|
);
|
|
}
|
|
|
|
$premium = $this->pickNumber($response, ['grossPremium', 'premium', 'netPremium', 'totalPremium']);
|
|
$idv = $this->pickNumber($response, ['idv', 'vehicleIDV', 'insuredDeclaredValue']);
|
|
if ($idv === null && isset($response['vehicle']['vehicleIDV']['idv'])) {
|
|
$idv = (float) $response['vehicle']['vehicleIDV']['idv'];
|
|
}
|
|
|
|
$vehicleIdv = $response['vehicle']['vehicleIDV'] ?? $response['vehicleIDV'] ?? [];
|
|
|
|
// Re-quote resets create/KYC progress so journey stays consistent.
|
|
$this->quoteModel->update($quoteId, [
|
|
'premium' => $premium,
|
|
'idv' => $idv,
|
|
'status' => 'QUOTED',
|
|
'quote_number' => null,
|
|
'application_id' => null,
|
|
'coverage_details' => json_encode($input['coverages'] ?? $payload['contract']['coverages'] ?? []),
|
|
]);
|
|
|
|
$vehicle = $this->vehicleModel->where('quote_id', $quoteId)->first();
|
|
if ($vehicle) {
|
|
$this->vehicleModel->update($vehicle['id'], [
|
|
'idv' => $idv ?? ($vehicleIdv['idv'] ?? null),
|
|
'default_idv' => $vehicleIdv['defaultIdv'] ?? $vehicleIdv['defaultIDV'] ?? null,
|
|
'minimum_idv' => $vehicleIdv['minimumIdv'] ?? $vehicleIdv['minIdv'] ?? null,
|
|
'maximum_idv' => $vehicleIdv['maximumIdv'] ?? $vehicleIdv['maxIdv'] ?? null,
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'enquiry_id' => $enquiryId,
|
|
'premium' => $premium,
|
|
'idv' => $idv,
|
|
'status' => 'QUOTED',
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Call motorCreateQuote for an existing quoted row.
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function createQuote(int $quoteId, array $input = []): array
|
|
{
|
|
$detail = $this->quoteModel->getDetail($quoteId);
|
|
if (!$detail) {
|
|
throw new DigitApiException('Quote not found.', '404', 404);
|
|
}
|
|
|
|
// Digit Create Quote rejects empty / invalid engine + chassis (VIN).
|
|
$vin = strtoupper(trim((string) ($input['vehicle_identification_number'] ?? ($detail['vehicle']['vehicle_identification_number'] ?? ''))));
|
|
$engine = strtoupper(trim((string) ($input['engine_number'] ?? ($detail['vehicle']['engine_number'] ?? ''))));
|
|
if ($vin === '' || strlen($vin) < 5) {
|
|
throw new DigitApiException('Please enter a valid chassis / VIN number.', '400', 422);
|
|
}
|
|
if ($engine === '' || strlen($engine) < 5) {
|
|
throw new DigitApiException('Please enter a valid engine number.', '400', 422);
|
|
}
|
|
|
|
$vehicle = $this->vehicleModel->where('quote_id', $quoteId)->first();
|
|
if ($vehicle) {
|
|
$this->vehicleModel->update($vehicle['id'], [
|
|
'vehicle_identification_number' => $vin,
|
|
'engine_number' => $engine,
|
|
]);
|
|
$detail['vehicle']['vehicle_identification_number'] = $vin;
|
|
$detail['vehicle']['engine_number'] = $engine;
|
|
}
|
|
|
|
$input['vehicle_identification_number'] = $vin;
|
|
$input['engine_number'] = $engine;
|
|
|
|
$policyholder = [
|
|
'first_name' => $input['first_name'] ?? null,
|
|
'last_name' => $input['last_name'] ?? null,
|
|
'mobile' => $input['mobile'] ?? null,
|
|
'email' => $input['email'] ?? null,
|
|
'pan' => $input['pan'] ?? null,
|
|
'dob' => $input['dob'] ?? null,
|
|
'address' => $input['address'] ?? null,
|
|
];
|
|
// Persist locally before Digit call so Back/reload keeps form data even if API fails.
|
|
$this->quoteModel->update($quoteId, [
|
|
'policyholder_details' => json_encode($policyholder),
|
|
'coverage_details' => json_encode($input['coverages'] ?? ($detail['coverage_details'] ?? [])),
|
|
]);
|
|
|
|
$payload = $this->buildCreateQuotePayload($detail, $input);
|
|
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['createQuote'],
|
|
$quoteId
|
|
);
|
|
|
|
$quoteNumber = $response['quoteNumber'] ?? $response['policyNumber'] ?? ($response['contract']['quoteNumber'] ?? null);
|
|
$applicationId = $response['applicationId'] ?? $response['policyId'] ?? ($response['contract']['applicationId'] ?? null);
|
|
$premium = $this->pickNumber($response, ['grossPremium', 'premium', 'netPremium', 'totalPremium']) ?? $detail['premium'];
|
|
|
|
$this->quoteModel->update($quoteId, [
|
|
'quote_number' => $quoteNumber,
|
|
'application_id' => $applicationId,
|
|
'premium' => $premium,
|
|
'start_date' => $input['start_date'] ?? $detail['start_date'],
|
|
'end_date' => $input['end_date'] ?? $detail['end_date'],
|
|
'status' => 'CREATED',
|
|
'policyholder_details' => json_encode($policyholder),
|
|
]);
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'quote_number' => $quoteNumber,
|
|
'application_id' => $applicationId,
|
|
'premium' => $premium,
|
|
'status' => 'CREATED',
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Poll KYC status and upsert motor_kyc.
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function kycStatus(int $quoteId): array
|
|
{
|
|
$detail = $this->quoteModel->getDetail($quoteId);
|
|
if (!$detail || empty($detail['quote_number'])) {
|
|
throw new DigitApiException('Quote number is required for KYC status. Create quote first.', '400', 400);
|
|
}
|
|
|
|
$payload = [
|
|
'queryParam' => [
|
|
'policyNumber' => $detail['quote_number'],
|
|
],
|
|
];
|
|
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['kycStatus'],
|
|
$quoteId
|
|
);
|
|
|
|
$status = $response['kycVerificationStatus']
|
|
?? $response['kycStatus']
|
|
?? ($response['kyc']['kycVerificationStatus'] ?? null);
|
|
|
|
$row = [
|
|
'quote_id' => $quoteId,
|
|
'kyc_id' => $response['kycId'] ?? ($response['kyc']['kycId'] ?? null),
|
|
'kyc_verification_status' => $status,
|
|
'reference_id' => $response['referenceId'] ?? ($response['kyc']['referenceId'] ?? null),
|
|
'link' => $response['link'] ?? ($response['kyc']['link'] ?? null),
|
|
'mismatch_type' => $response['mismatchType'] ?? null,
|
|
'id_verification_doc_type' => $response['idVerificationDocType'] ?? null,
|
|
'address_verification_doc_type' => $response['addressVerificationDocType'] ?? null,
|
|
'mode' => $response['mode'] ?? 'O',
|
|
'checked_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
$existing = $this->kycModel->where('quote_id', $quoteId)->orderBy('id', 'DESC')->first();
|
|
if ($existing) {
|
|
$this->kycModel->update($existing['id'], $row);
|
|
$row['id'] = $existing['id'];
|
|
} else {
|
|
$row['id'] = $this->kycModel->insert($row);
|
|
}
|
|
|
|
$normalized = strtoupper((string) $status);
|
|
if (in_array($normalized, ['DONE', 'VERIFIED', 'SUCCESS', 'COMPLETED'], true)) {
|
|
$this->quoteModel->update($quoteId, ['status' => 'KYC_DONE']);
|
|
}
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'kyc' => $row,
|
|
'status' => $status,
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Generate payment link.
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function paymentLink(int $quoteId, array $input = []): array
|
|
{
|
|
$detail = $this->quoteModel->getDetail($quoteId);
|
|
if (!$detail || empty($detail['application_id'])) {
|
|
throw new DigitApiException('Application ID is required for payment. Create quote first.', '400', 400);
|
|
}
|
|
|
|
// Prefer UI override, then stored premium; Digit expects "INR 1234.56"
|
|
$premiumValue = null;
|
|
if (!empty($input['premium_amount'])) {
|
|
$premiumValue = $this->parseMoneyAmount($input['premium_amount']);
|
|
}
|
|
if ($premiumValue === null) {
|
|
$premiumValue = $this->parseMoneyAmount($detail['premium'] ?? null);
|
|
}
|
|
if ($premiumValue === null || $premiumValue <= 0) {
|
|
throw new DigitApiException(
|
|
'Premium amount is missing or zero. Re-run Create Quote before generating payment link.',
|
|
'400',
|
|
422
|
|
);
|
|
}
|
|
|
|
$premiumAmount = 'INR ' . number_format($premiumValue, 2, '.', '');
|
|
|
|
$successUrl = $input['success_return_url']
|
|
?? base_url('digit-motor/payment/callback/success/' . $quoteId);
|
|
$cancelUrl = $input['cancel_return_url']
|
|
?? base_url('digit-motor/payment/callback/cancel/' . $quoteId);
|
|
|
|
$payload = [
|
|
'premiumAmount' => $premiumAmount,
|
|
'paymentMode' => $input['payment_mode'] ?? 'EB',
|
|
'successReturnUrl' => $successUrl,
|
|
'cancelReturnUrl' => $cancelUrl,
|
|
'applicationId' => $detail['application_id'],
|
|
];
|
|
|
|
// When Digit's ABS lookup fails, payment API asks for create-quote payload as absContractDetails.
|
|
$cqResponse = $this->latestCreateQuoteResponse($quoteId);
|
|
if (is_array($cqResponse) && $cqResponse !== []) {
|
|
$payload['absContractDetails'] = $cqResponse;
|
|
}
|
|
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['payment'],
|
|
$quoteId
|
|
);
|
|
|
|
$dispatcher = $response['dispatcherResponse']
|
|
?? $response['paymentLink']
|
|
?? $response['redirectUrl']
|
|
?? null;
|
|
|
|
$row = [
|
|
'quote_id' => $quoteId,
|
|
'application_id' => $detail['application_id'],
|
|
'digit_payment_id' => $response['digitPaymentId'] ?? $response['paymentId'] ?? null,
|
|
'request_reference' => $response['requestReference'] ?? null,
|
|
'payment_mode' => $payload['paymentMode'],
|
|
'cancel_return_url' => $cancelUrl,
|
|
'success_return_url' => $successUrl,
|
|
'dispatcher_response' => $dispatcher,
|
|
'premium' => $premiumValue,
|
|
'payment_status' => 'LINK_GENERATED',
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
$row['id'] = $this->paymentModel->insert($row);
|
|
|
|
// Keep quote premium in sync if it was previously null/zero
|
|
if (empty($detail['premium']) || (float) $detail['premium'] <= 0) {
|
|
$this->quoteModel->update($quoteId, ['premium' => $premiumValue]);
|
|
}
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'payment' => $row,
|
|
'dispatcher_response'=> $dispatcher,
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Last successful Create Quote response for this quote (used as absContractDetails).
|
|
*/
|
|
protected function latestCreateQuoteResponse(int $quoteId): ?array
|
|
{
|
|
$logModel = new MotorApiLogModel();
|
|
$row = $logModel
|
|
->where('quote_id', $quoteId)
|
|
->where('integration_id', $this->config->integrationIds['createQuote'])
|
|
->where('http_status', 200)
|
|
->orderBy('id', 'DESC')
|
|
->first();
|
|
|
|
if (!$row || empty($row['response_body'])) {
|
|
return null;
|
|
}
|
|
|
|
$body = $row['response_body'];
|
|
if (is_string($body)) {
|
|
$decoded = json_decode($body, true);
|
|
return is_array($decoded) ? $decoded : null;
|
|
}
|
|
|
|
return is_array($body) ? $body : null;
|
|
}
|
|
|
|
/**
|
|
* Poll policy status.
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function policyStatus(int $quoteId): array
|
|
{
|
|
$detail = $this->quoteModel->getDetail($quoteId);
|
|
if (!$detail || empty($detail['quote_number'])) {
|
|
throw new DigitApiException('Quote/policy number is required for policy status.', '400', 400);
|
|
}
|
|
|
|
$payload = [
|
|
'queryParam' => [
|
|
'policyNumber' => $detail['quote_number'],
|
|
],
|
|
];
|
|
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['policyStatus'],
|
|
$quoteId
|
|
);
|
|
|
|
$policyStatus = $response['policyStatus']
|
|
?? $response['status']
|
|
?? ($response['policy']['policyStatus'] ?? null);
|
|
$policyNumber = $response['policyNumber'] ?? $detail['quote_number'];
|
|
|
|
$row = [
|
|
'quote_id' => $quoteId,
|
|
'policy_number' => $policyNumber,
|
|
'policy_status' => $policyStatus,
|
|
'response_code' => isset($response['responseCode']) ? (string) $response['responseCode'] : null,
|
|
'response_message' => $response['responseMessage'] ?? null,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
$existing = $this->policyModel->where('quote_id', $quoteId)->first();
|
|
if ($existing) {
|
|
$this->policyModel->update($existing['id'], $row);
|
|
$row['id'] = $existing['id'];
|
|
$row['schedule_path'] = $existing['schedule_path'];
|
|
$row['proposal_path'] = $existing['proposal_path'];
|
|
} else {
|
|
$row['id'] = $this->policyModel->insert($row);
|
|
}
|
|
|
|
$normalized = strtoupper((string) $policyStatus);
|
|
if (in_array($normalized, ['EFFECTIVE', 'COMPLETE', 'COMPLETED', 'ACTIVE'], true)) {
|
|
$this->quoteModel->update($quoteId, ['status' => 'EFFECTIVE']);
|
|
if (!empty($detail['payment'])) {
|
|
$this->paymentModel->update($detail['payment']['id'], ['payment_status' => 'PAID']);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'policy' => $row,
|
|
'status' => $policyStatus,
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Fetch policy PDF paths (only after policy is effective-ish).
|
|
*
|
|
* @throws DigitApiException
|
|
*/
|
|
public function policyPdf(int $quoteId): array
|
|
{
|
|
$detail = $this->quoteModel->getDetail($quoteId);
|
|
if (!$detail) {
|
|
throw new DigitApiException('Quote not found.', '404', 404);
|
|
}
|
|
|
|
$policyId = $detail['application_id']
|
|
?? ($detail['policy']['policy_number'] ?? null);
|
|
if (empty($policyId)) {
|
|
throw new DigitApiException('Application/policy ID is required for PDF.', '400', 400);
|
|
}
|
|
|
|
$policyStatus = strtoupper((string) ($detail['policy']['policy_status'] ?? $detail['status'] ?? ''));
|
|
if (!in_array($policyStatus, ['EFFECTIVE', 'COMPLETE', 'COMPLETED', 'ACTIVE', 'PAID'], true)
|
|
&& !in_array(strtoupper((string) $detail['status']), ['EFFECTIVE', 'PAID'], true)) {
|
|
throw new DigitApiException(
|
|
'Policy PDF is available only after the policy is effective. Current status: ' . ($detail['status'] ?? 'unknown'),
|
|
'400',
|
|
400
|
|
);
|
|
}
|
|
|
|
$payload = [
|
|
'policyId' => $policyId,
|
|
'headerParam' => [
|
|
'Authorization' => $this->config->pdfAuthKey,
|
|
],
|
|
];
|
|
|
|
$response = $this->api->post(
|
|
$this->config->executorPath,
|
|
$payload,
|
|
$this->config->integrationIds['policyPdf'],
|
|
$quoteId
|
|
);
|
|
|
|
$schedulePath = $response['schedulePath'] ?? $response['policySchedulePath'] ?? ($response['schedule'] ?? null);
|
|
$proposalPath = $response['proposalPath'] ?? $response['proposalFormPath'] ?? ($response['proposal'] ?? null);
|
|
|
|
$row = [
|
|
'quote_id' => $quoteId,
|
|
'policy_number' => $detail['quote_number'],
|
|
'policy_status' => $detail['policy']['policy_status'] ?? $detail['status'],
|
|
'schedule_path' => $schedulePath,
|
|
'proposal_path' => $proposalPath,
|
|
'response_code' => isset($response['responseCode']) ? (string) $response['responseCode'] : null,
|
|
'response_message' => $response['responseMessage'] ?? null,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
$existing = $this->policyModel->where('quote_id', $quoteId)->first();
|
|
if ($existing) {
|
|
$this->policyModel->update($existing['id'], $row);
|
|
$row['id'] = $existing['id'];
|
|
} else {
|
|
$row['id'] = $this->policyModel->insert($row);
|
|
}
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'policy' => $row,
|
|
'response' => $response,
|
|
];
|
|
}
|
|
|
|
protected function persistQuoteShell(array $input, string $enquiryId, string $status): int
|
|
{
|
|
$quoteIdIn = (int) ($input['quote_id'] ?? 0);
|
|
$existing = null;
|
|
|
|
if ($quoteIdIn > 0) {
|
|
$existing = $this->quoteModel->find($quoteIdIn);
|
|
}
|
|
if (!$existing && $enquiryId !== '') {
|
|
$existing = $this->quoteModel->where('enquiry_id', $enquiryId)->first();
|
|
}
|
|
|
|
$quoteData = [
|
|
'enquiry_id' => $existing['enquiry_id'] ?? $enquiryId,
|
|
'policy_holder_type' => $input['policy_holder_type'] ?? 'INDIVIDUAL',
|
|
'insurance_product_code' => (string) ($input['insurance_product_code'] ?? '20101'),
|
|
'sub_insurance_product_code' => (string) ($input['sub_insurance_product_code'] ?? 'PB'),
|
|
'previous_insurer_code' => $input['previous_insurer_code'] ?? null,
|
|
'previous_policy_expiry_date'=> $input['previous_policy_expiry_date'] ?? null,
|
|
'external_policy_number' => $input['previous_policy_number'] ?? null,
|
|
'is_ncb_transfer' => !empty($input['is_ncb_transfer']) ? 1 : 0,
|
|
'start_date' => $input['start_date'] ?? null,
|
|
'end_date' => $input['end_date'] ?? null,
|
|
'pincode' => (string) ($input['pincode'] ?? ''),
|
|
'coverage_details' => json_encode($input['coverages'] ?? []),
|
|
];
|
|
|
|
// Only stamp DRAFT on brand-new rows; existing rows keep status until QQ success.
|
|
if (!$existing) {
|
|
$quoteData['status'] = $status;
|
|
}
|
|
|
|
if ($existing) {
|
|
$quoteId = (int) $existing['id'];
|
|
$this->quoteModel->update($quoteId, $quoteData);
|
|
$vehicle = $this->vehicleModel->where('quote_id', $quoteId)->first();
|
|
$vehicleData = $this->vehicleRowFromInput($input, $quoteId);
|
|
if ($vehicle) {
|
|
$this->vehicleModel->update($vehicle['id'], $vehicleData);
|
|
} else {
|
|
$this->vehicleModel->insert($vehicleData);
|
|
}
|
|
return $quoteId;
|
|
}
|
|
|
|
$quoteId = (int) $this->quoteModel->insert($quoteData);
|
|
$this->vehicleModel->insert($this->vehicleRowFromInput($input, $quoteId));
|
|
return $quoteId;
|
|
}
|
|
|
|
protected function vehicleRowFromInput(array $input, int $quoteId): array
|
|
{
|
|
$plate = strtoupper(preg_replace('/\s+/', '', (string) ($input['license_plate_number'] ?? '')));
|
|
|
|
return [
|
|
'quote_id' => $quoteId,
|
|
'is_vehicle_new' => !empty($input['is_vehicle_new']) ? 1 : 0,
|
|
'vehicle_maincode' => (string) ($input['vehicle_maincode'] ?? ''),
|
|
'license_plate_number' => $plate,
|
|
'vehicle_identification_number' => $input['vehicle_identification_number'] ?? null,
|
|
'engine_number' => $input['engine_number'] ?? null,
|
|
'manufacture_date' => $input['manufacture_date'] ?? date('Y-m-d'),
|
|
'registration_date' => $input['registration_date'] ?? date('Y-m-d'),
|
|
'idv' => $input['idv'] ?? 0,
|
|
];
|
|
}
|
|
|
|
protected function buildQuickQuotePayload(array $input, string $enquiryId): array
|
|
{
|
|
$plate = strtoupper(preg_replace('/\s+/', '', (string) ($input['license_plate_number'] ?? '')));
|
|
$authority = $input['registration_authority'] ?? (strlen($plate) >= 4 ? substr($plate, 0, 4) : '');
|
|
$coverages = $this->defaultCoverages($input['coverages'] ?? []);
|
|
|
|
return [
|
|
'vehicle' => [
|
|
'isVehicleNew' => !empty($input['is_vehicle_new']),
|
|
'vehicleIDV' => [
|
|
'idv' => (string) ($input['idv'] ?? '0'),
|
|
],
|
|
'vehicleMaincode' => (string) ($input['vehicle_maincode'] ?? ''),
|
|
'licensePlateNumber' => $plate,
|
|
'registrationAuthority' => $authority,
|
|
'vehicleIdentificationNumber' => $input['vehicle_identification_number'] ?? '',
|
|
'engineNumber' => $input['engine_number'] ?? '',
|
|
'manufactureDate' => $input['manufacture_date'] ?? null,
|
|
'registrationDate' => $input['registration_date'] ?? null,
|
|
'fastTagNumber' => null,
|
|
'ownershipSerialNumber' => 0,
|
|
],
|
|
'previousInsurer' => [
|
|
'isPreviousInsurerKnown' => !empty($input['previous_insurer_code']),
|
|
'previousInsurerCode' => isset($input['previous_insurer_code']) ? (string) $input['previous_insurer_code'] : '',
|
|
'previousPolicyExpiryDate' => $input['previous_policy_expiry_date'] ?? null,
|
|
'previousPolicyNumber' => $input['previous_policy_number'] ?? '',
|
|
'isClaimInLastYear' => !empty($input['is_claim_in_last_year']),
|
|
'previousNoClaimBonus' => $input['previous_ncb'] ?? 'ZERO',
|
|
'previousPolicyType' => $input['previous_policy_type'] ?? '',
|
|
],
|
|
'contract' => [
|
|
'insuranceProductCode' => (int) ($input['insurance_product_code'] ?? 20102),
|
|
'startDate' => $input['start_date'] ?? null,
|
|
'endDate' => $input['end_date'] ?? null,
|
|
'policyTerm' => (int) ($input['policy_term'] ?? 1),
|
|
'coverages' => $coverages,
|
|
'subInsuranceProductCode' => $input['sub_insurance_product_code'] ?? 'PB',
|
|
'policyHolderType' => $input['policy_holder_type'] ?? 'INDIVIDUAL',
|
|
'externalPolicyNumber' => $input['external_policy_number'] ?? '',
|
|
],
|
|
'pinCode' => (int) ($input['pincode'] ?? 0),
|
|
'enquiryId' => $enquiryId,
|
|
];
|
|
}
|
|
|
|
protected function buildCreateQuotePayload(array $detail, array $input): array
|
|
{
|
|
$vehicle = $detail['vehicle'] ?? [];
|
|
$coverages = $input['coverages']
|
|
?? (is_array($detail['coverage_details']) ? $detail['coverage_details'] : $this->defaultCoverages([]));
|
|
|
|
$plate = $vehicle['license_plate_number'] ?? '';
|
|
$authority = $vehicle['registration_authority'] ?? (strlen($plate) >= 4 ? substr($plate, 0, 4) : '');
|
|
|
|
$startDate = $input['start_date'] ?? $detail['start_date'];
|
|
$endDate = $input['end_date'] ?? $detail['end_date'];
|
|
|
|
$payload = [
|
|
'enquiryId' => $detail['enquiry_id'],
|
|
'contract' => [
|
|
'insuranceProductCode' => (string) ($detail['insurance_product_code'] ?? '20101'),
|
|
'subInsuranceProductCode' => (string) ($detail['sub_insurance_product_code'] ?? 'PB'),
|
|
'startDate' => $startDate,
|
|
'endDate' => $endDate,
|
|
'policyHolderType' => $detail['policy_holder_type'] ?? 'INDIVIDUAL',
|
|
'externalPolicyNumber' => $detail['external_policy_number'],
|
|
'isNCBTransfer' => !empty($detail['is_ncb_transfer']) ? true : null,
|
|
'coverages' => $coverages,
|
|
],
|
|
'vehicle' => [
|
|
'isVehicleNew' => !empty($vehicle['is_vehicle_new']) ? 'false' : 'false',
|
|
'vehicleMaincode' => $vehicle['vehicle_maincode'] ?? '',
|
|
'licensePlateNumber' => $plate,
|
|
'vehicleIdentificationNumber' => $input['vehicle_identification_number']
|
|
?? ($vehicle['vehicle_identification_number'] ?? ''),
|
|
'registrationAuthority' => $authority,
|
|
'engineNumber' => $input['engine_number']
|
|
?? ($vehicle['engine_number'] ?? ''),
|
|
'manufactureDate' => $vehicle['manufacture_date'] ?? null,
|
|
'registrationDate' => $vehicle['registration_date'] ?? null,
|
|
'vehicleIDV' => [
|
|
'idv' => (float) ($vehicle['idv'] ?? $detail['idv'] ?? 0),
|
|
],
|
|
'usageType' => null,
|
|
'permitType' => null,
|
|
'motorType' => null,
|
|
],
|
|
'hypothecation' => [
|
|
'isHypothecation' => false,
|
|
'hypothecationAgency' => '',
|
|
'hypothecationCIty' => '',
|
|
],
|
|
'previousInsurer' => [
|
|
'isPreviousInsurerKnown' => !empty($detail['previous_insurer_code']),
|
|
'previousInsurerCode' => isset($detail['previous_insurer_code']) ? (string) $detail['previous_insurer_code'] : '',
|
|
'previousPolicyNumber' => $detail['external_policy_number'] ?? '',
|
|
'previousPolicyExpiryDate' => $detail['previous_policy_expiry_date'] ?? null,
|
|
'isClaimInLastYear' => $input['is_claim_in_last_year'] ?? 'false',
|
|
'originalPreviousPolicyType' => $input['previous_policy_type'] ?? '1OD_1TP',
|
|
'previousPolicyType' => $input['previous_policy_type'] ?? '1OD_1TP',
|
|
'previousNoClaimBonus' => $input['previous_ncb'] ?? 'ZERO',
|
|
'currentThirdPartyPolicy' => null,
|
|
],
|
|
'pospInfo' => [
|
|
'isPOSP' => 'false',
|
|
'pospName' => null,
|
|
'pospUniqueNumber' => null,
|
|
'pospLocation' => '',
|
|
'pospPanNumber' => null,
|
|
'pospAadhaarNumber' => null,
|
|
'pospContactNumber' => null,
|
|
],
|
|
'persons' => $input['persons'] ?? [$this->defaultPerson($input, $detail)],
|
|
'dealer' => [
|
|
'dealerName' => '',
|
|
'city' => '',
|
|
'deliveryDate' => null,
|
|
],
|
|
'motorQuestions' => [
|
|
'furtherAgreement' => '',
|
|
'selfInspection' => false,
|
|
'financer' => '',
|
|
],
|
|
'motorBreakIn' => [
|
|
'isBreakin' => false,
|
|
'breakinExcess' => null,
|
|
'breakinComments' => null,
|
|
'isPreInspectionWaived' => false,
|
|
'isPreInspectionCompleted' => null,
|
|
'isDocumentUploaded' => null,
|
|
],
|
|
'kyc' => $input['kyc'] ?? [
|
|
'isKYCDone' => !empty($input['kyc_id']),
|
|
'ckycReferenceDocId' => $input['ckyc_doc_id'] ?? 'D07',
|
|
'ckycReferenceNumber' => $input['ckyc_reference'] ?? ($input['pan'] ?? ''),
|
|
'dateOfBirth' => $input['dob'] ?? null,
|
|
'photo' => '',
|
|
],
|
|
'nominee' => $input['nominee'] ?? [
|
|
'firstName' => $input['nominee_first_name'] ?? 'NOMINEE',
|
|
'middleName' => '',
|
|
'lastName' => $input['nominee_last_name'] ?? '',
|
|
'dateOfBirth' => $input['nominee_dob'] ?? '1990-01-01',
|
|
'relation' => $input['nominee_relation'] ?? 'OTHER',
|
|
'personType' => 'INDIVIDUAL',
|
|
],
|
|
];
|
|
|
|
// Fix isVehicleNew flag properly
|
|
$payload['vehicle']['isVehicleNew'] = !empty($vehicle['is_vehicle_new']) ? 'true' : 'false';
|
|
|
|
return $payload;
|
|
}
|
|
|
|
protected function defaultPerson(array $input, array $detail): array
|
|
{
|
|
$pincode = $detail['pincode'] ?? ($input['pincode'] ?? '');
|
|
return [
|
|
'personType' => 'INDIVIDUAL',
|
|
'addresses' => [[
|
|
'addressType' => 'PRIMARY_RESIDENCE',
|
|
'flatNumber' => null,
|
|
'streetNumber' => null,
|
|
'street' => $input['address'] ?? '',
|
|
'district' => '',
|
|
'state' => (string) ($input['state_code'] ?? ''),
|
|
'city' => $input['city'] ?? '',
|
|
'country' => 'IN',
|
|
'pincode' => (string) $pincode,
|
|
]],
|
|
'communications' => [
|
|
[
|
|
'communicationType' => 'MOBILE',
|
|
'communicationId' => $input['mobile'] ?? '9999999999',
|
|
'isPrefferedCommunication' => true,
|
|
],
|
|
[
|
|
'communicationType' => 'EMAIL',
|
|
'communicationId' => $input['email'] ?? 'noreply@nhance.local',
|
|
'isPrefferedCommunication' => true,
|
|
],
|
|
],
|
|
'identificationDocuments' => [
|
|
[
|
|
'issuingPlace' => 'IN',
|
|
'documentType' => 'PAN_CARD',
|
|
'documentId' => $input['pan'] ?? '',
|
|
],
|
|
],
|
|
'isPolicyHolder' => true,
|
|
'isVehicleOwner' => true,
|
|
'firstName' => $input['first_name'] ?? 'CUSTOMER',
|
|
'middleName' => null,
|
|
'lastName' => $input['last_name'] ?? '',
|
|
'dateOfBirth' => $input['dob'] ?? '1990-01-01',
|
|
'gender' => $input['gender'] ?? 'MALE',
|
|
'isDriver' => true,
|
|
'isInsuredPerson' => true,
|
|
];
|
|
}
|
|
|
|
protected function defaultCoverages(array $overrides): array
|
|
{
|
|
$base = [
|
|
'isIMT23' => false,
|
|
'personalAccident' => [
|
|
'selection' => !empty($overrides['personal_accident'] ?? true),
|
|
'insuredAmount' => 1500000,
|
|
'coverTerm' => 1,
|
|
],
|
|
'addons' => [
|
|
'personalBelonging' => ['selection' => !empty($overrides['personal_belonging'])],
|
|
'returnToInvoice' => ['selection' => !empty($overrides['return_to_invoice'])],
|
|
'rimProtection' => ['selection' => !empty($overrides['rim_protection'])],
|
|
'consumables' => ['selection' => !empty($overrides['consumables'])],
|
|
'partsDepreciation' => [
|
|
'selection' => !empty($overrides['zero_dep'] ?? $overrides['parts_depreciation']),
|
|
'claimsCovered' => $overrides['claims_covered'] ?? 'TWO',
|
|
],
|
|
'engineProtection' => ['selection' => !empty($overrides['engine_protect'])],
|
|
'tyreProtection' => ['selection' => !empty($overrides['tyre_protect'])],
|
|
'roadSideAssistance' => ['selection' => !empty($overrides['rsa'] ?? $overrides['roadside_assistance'])],
|
|
'keyAndLockProtect' => ['selection' => !empty($overrides['key_protect'])],
|
|
],
|
|
'accessories' => [
|
|
'electrical' => ['selection' => false, 'insuredAmount' => 0],
|
|
'nonElectrical' => ['selection' => false, 'insuredAmount' => 0],
|
|
'cng' => ['selection' => false, 'insuredAmount' => 0],
|
|
],
|
|
'voluntaryDeductible' => $overrides['voluntary_deductible'] ?? 'ZERO',
|
|
'isGeoExt' => null,
|
|
'legalLiability' => [
|
|
'nonFarePaxLL' => ['selection' => false, 'insuredCount' => 1],
|
|
'unnamedPaxLL' => ['selection' => false, 'insuredCount' => 0],
|
|
'workersCompensationLL' => ['selection' => false, 'insuredCount' => 1],
|
|
'paidDriverLL' => ['selection' => true, 'insuredCount' => 1],
|
|
'employeesLL' => ['selection' => false, 'insuredCount' => 1],
|
|
'cleanersLL' => ['selection' => false, 'insuredCount' => 1],
|
|
],
|
|
'thirdPartyLiability' => [
|
|
'isTPPD' => array_key_exists('tppd', $overrides) ? (bool) $overrides['tppd'] : true,
|
|
],
|
|
'unnamedPA' => [
|
|
'unnamedPaidDriver' => ['selection' => false, 'insuredAmount' => 100000, 'insuredCount' => 1],
|
|
'unnamedPax' => ['selection' => false, 'insuredAmount' => 0, 'insuredCount' => 4],
|
|
'unnamedConductor' => ['selection' => false, 'insuredAmount' => 100000, 'insuredCount' => 1],
|
|
'unnamedHirer' => ['selection' => false, 'insuredAmount' => 100000, 'insuredCount' => 1],
|
|
'unnamedPillionRider'=> ['selection' => false, 'insuredAmount' => 100000, 'insuredCount' => 1],
|
|
'unnamedCleaner' => ['selection' => false, 'insuredAmount' => 100000, 'insuredCount' => 1],
|
|
],
|
|
'isOverturningExclusionIMT47' => false,
|
|
'isTheftAndConversionRiskIMT43' => false,
|
|
'ownDamage' => [
|
|
'discount' => [
|
|
'userSpecialDiscountPercent' => 0,
|
|
'discounts' => [],
|
|
],
|
|
'surcharge' => [
|
|
'loadings' => [],
|
|
],
|
|
],
|
|
];
|
|
|
|
// If caller passed a Digit-shaped coverages object already, prefer it
|
|
if (isset($overrides['addons']) || isset($overrides['thirdPartyLiability'])) {
|
|
return array_replace_recursive($base, $overrides);
|
|
}
|
|
|
|
return $base;
|
|
}
|
|
|
|
protected function pickNumber(array $response, array $keys): ?float
|
|
{
|
|
foreach ($keys as $key) {
|
|
if (!array_key_exists($key, $response)) {
|
|
continue;
|
|
}
|
|
$parsed = $this->parseMoneyAmount($response[$key]);
|
|
if ($parsed !== null) {
|
|
return $parsed;
|
|
}
|
|
}
|
|
if (isset($response['premiumBreakUp']) && is_array($response['premiumBreakUp'])) {
|
|
foreach (['grossPremium', 'totalPremium', 'netPremium'] as $k) {
|
|
if (!array_key_exists($k, $response['premiumBreakUp'])) {
|
|
continue;
|
|
}
|
|
$parsed = $this->parseMoneyAmount($response['premiumBreakUp'][$k]);
|
|
if ($parsed !== null) {
|
|
return $parsed;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Digit returns money as numbers or strings like "INR 4030.88".
|
|
*/
|
|
protected function parseMoneyAmount($value): ?float
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
if (is_int($value) || is_float($value)) {
|
|
return (float) $value;
|
|
}
|
|
if (!is_string($value)) {
|
|
return null;
|
|
}
|
|
if (is_numeric($value)) {
|
|
return (float) $value;
|
|
}
|
|
if (preg_match('/([0-9]+(?:\.[0-9]+)?)/', $value, $m)) {
|
|
return (float) $m[1];
|
|
}
|
|
return null;
|
|
}
|
|
}
|