env('UCIC_API_KEY'), * 'api_secret' => env('UCIC_API_SECRET'), * 'base_url' => env('UCIC_BASE_URL'), * 'source' => env('UCIC_SOURCE', 'CREGO'), * 'product_name' => env('UCIC_PRODUCT_NAME', 'Working Capital Loan'), */ class UCICService { // ------------------------------------------------------------------------- // Configuration // ------------------------------------------------------------------------- protected $baseUrl; protected $apiKey; protected $apiSecret; protected $source; protected $productName; // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- public function __construct() { $this->baseUrl = rtrim(config('services.ucic.base_url', ''), '/'); $this->apiKey = config('services.ucic.api_key', ''); $this->apiSecret = config('services.ucic.api_secret', ''); $this->source = config('services.ucic.source', 'CREGO'); $this->productName = config('services.ucic.product_name', 'Working Capital Loan'); } // ========================================================================= // PUBLIC API METHODS // ========================================================================= /** * Push a full onboarding payload to UCIC. * * @param array $params Keys: * - anchor_id (string) * - business_id (string) * - product_name (string, optional override) * - business_data (array) * - business_person_data (array of arrays) * - anchor_data (array) * - anchor_person_data (array of arrays) * - loan_data (array of arrays) * @return Response */ public function pushOnboarding(array $params) { $payload = $this->buildOnboardingPayload($params); return $this->post('/api/onboarding', $payload); } // ========================================================================= // PAYLOAD BUILDERS // ========================================================================= /** * Build the top-level onboarding payload. */ protected function buildOnboardingPayload(array $params): array { return [ 'data' => [ 'source' => $this->source, 'productName' => $params['product_name'] ?? $this->productName, 'anchorId' => $params['anchor_id'] ?? null, 'businessId' => $params['business_id'] ?? null, 'businessData' => $this->buildBusinessData($params['business_data'] ?? []), 'businessPersonData' => $this->buildPersonDataCollection($params['business_person_data'] ?? []), 'anchorData' => $this->buildAnchorData($params['anchor_data'] ?? []), 'anchorPersonData' => $this->buildPersonDataCollection($params['anchor_person_data'] ?? []), 'loanData' => $this->buildLoanDataCollection($params['loan_data'] ?? []), ], ]; } /** * Build businessData node. */ protected function buildBusinessData(array $data): array { return $this->filterNull([ 'refId' => $data['ref_id'] ?? null, 'name' => $data['name'] ?? null, 'gstin' => $data['gstin'] ?? null, 'pan' => $data['pan'] ?? null, 'customerType' => $data['customer_type'] ?? null, 'customerConstitution' => $data['customer_constitution'] ?? null, 'email' => $data['email'] ?? null, 'mobile' => $data['mobile'] ?? null, 'telephone' => $data['telephone'] ?? null, 'cin' => $data['cin'] ?? null, 'address' => $this->buildAddressCollection($data['address'] ?? []), ]); } /** * Build anchorData node. */ protected function buildAnchorData(array $data): array { return $this->filterNull([ 'refId' => $data['ref_id'] ?? null, 'name' => $data['name'] ?? null, 'pan' => $data['pan'] ?? null, 'customerType' => $data['customer_type'] ?? null, 'customerConstitution' => $data['customer_constitution'] ?? null, 'email' => $data['email'] ?? null, 'mobile' => $data['mobile'] ?? null, 'cin' => $data['cin'] ?? null, 'address' => $this->buildAddressCollection($data['address'] ?? []), ]); } /** * Build a collection of person/entity data nodes. * Each entry may optionally contain `related_parties` (e.g. partners of a guarantor). */ protected function buildPersonDataCollection(array $items): array { return array_map(function($item) { return $this->buildPersonData($item); }, $items); } /** * Build a single person / entity node. * Handles both individuals and non-individual entities uniformly. */ protected function buildPersonData(array $data): array { $node = $this->filterNull([ 'refId' => $data['ref_id'] ?? null, 'name' => $data['name'] ?? null, 'pan' => $data['pan'] ?? null, 'mobile' => $data['mobile'] ?? null, 'customerConstitution' => $data['customer_constitution'] ?? null, 'customerType' => $data['customer_type'] ?? null, 'email' => $data['email'] ?? null, 'gender' => $data['gender'] ?? null, 'fatherName' => $data['father_name'] ?? null, 'spouseName' => $data['spouse_name'] ?? null, 'dob' => $data['dob'] ?? null, 'maritalStatus' => $data['marital_status'] ?? null, 'profession' => $data['profession'] ?? null, 'passportNumber' => $data['passport_number'] ?? null, 'voterId' => $data['voter_id'] ?? null, 'address' => $this->buildAddressCollection($data['address'] ?? []), ]); // Recursively handle relatedParties (e.g. partners within a guarantor entity) if (!empty($data['related_parties'])) { $node['relatedParties'] = $this->buildPersonDataCollection($data['related_parties']); } return $node; } /** * Build a collection of address nodes. */ protected function buildAddressCollection(array $addresses): array { return array_map(function($addr) { return $this->buildAddress($addr); }, $addresses); } /** * Build a single address node. * Handles snake_case, camelCase and mixed pincode key variants. */ protected function buildAddress(array $addr): array { return $this->filterNull([ 'type' => $addr['type'] ?? null, 'address' => $addr['address'] ?? null, 'area' => $addr['area'] ?? null, 'city' => $addr['city'] ?? null, 'state' => $addr['state'] ?? null, 'pinCode' => $addr['pin_code'] ?? $addr['pinCode'] ?? $addr['pincode'] ?? null, 'country' => $addr['country'] ?? null, 'wef' => $addr['wef'] ?? null, 'applicationId' => $addr['application_id'] ?? null, 'loanId' => $addr['loan_id'] ?? null, 'segmentId' => $addr['segment_id'] ?? null, ]); } /** * Build a collection of loan data nodes. */ protected function buildLoanDataCollection(array $items): array { return array_map(function($item) { return $this->buildLoanData($item); }, $items); } /** * Build a single loan data node. */ protected function buildLoanData(array $data): array { return $this->filterNull([ 'applicationId' => $data['application_id'] ?? null, 'loanId' => $data['loan_id'] ?? null, 'segmentId' => $data['segment_id'] ?? null, ]); } // ========================================================================= // HTTP CLIENT // ========================================================================= /** * Make a POST request to the UCIC API. */ protected function post($endpoint, array $payload) { return $this->executeCurl('POST', $this->baseUrl . $endpoint, $payload); } /** * Make a GET request to the UCIC API. */ protected function get($endpoint, array $query = []) { $url = $this->baseUrl . $endpoint; if (!empty($query)) { $url .= '?' . http_build_query($query); } return $this->executeCurl('GET', $url); } protected function executeCurl($method, $url, array $payload = []) { try { $ch = curl_init(); $headers = []; foreach ($this->buildHeaders() as $key => $value) { $headers[] = "$key: $value"; } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_TIMEOUT, 30); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); } $responseBody = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new \Exception("cURL Error: " . $error); } $response = new UcicHttpResponse($statusCode, (string)$responseBody); $this->logRequest($method, $url, $payload, $response); return $response; } catch (\Throwable $e) { Log::error("[UCICService] $method request failed", [ 'url' => $url, 'error' => $e->getMessage(), 'payload' => $payload, ]); throw $e; } } // ========================================================================= // AUTHENTICATION / HEADERS // ========================================================================= /** * Build the request headers with API auth. * * x-api-key : the raw API key * x-api-token : JWT HS256 token — mirrors the Python pattern: * jwt.encode({"key": api_key}, api_secret, algorithm="HS256") */ protected function buildHeaders(): array { return [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'x-api-key' => $this->apiSecret, 'x-api-token' => $this->generateApiToken(), ]; } /** * Generate the x-api-token JWT using Firebase\JWT (same library as JWTAccess.php). * * Python equivalent: * token = jwt.encode({"key": api_key}, api_secret, algorithm="HS256") */ protected function generateApiToken(): string { $payload = [ 'key' => $this->apiKey, 'exp' => time() + 300, ]; return JWT::encode($payload, $this->apiSecret, 'HS256'); } // ========================================================================= // UTILITIES // ========================================================================= /** * Strip null and empty-array values from a single-level array. */ protected function filterNull(array $data): array { return array_filter($data, function($value) { return $value !== null && $value !== []; }); } /** * Log API request/response details (request body is omitted from logs for security). */ protected function logRequest(string $method, string $url, array $data, UcicHttpResponse $response) { Log::info('[UCICService] API Call', [ 'method' => $method, 'url' => $url, 'status' => $response->status(), 'success' => $response->successful(), 'response' => $response->json(), ]); } } class UcicHttpResponse { protected $statusCode; protected $body; public function __construct($statusCode, $body) { $this->statusCode = $statusCode; $this->body = $body; } public function successful() { return $this->statusCode >= 200 && $this->statusCode < 300; } public function status() { return $this->statusCode; } public function body() { return $this->body; } public function json() { return json_decode($this->body, true); } }