external_customer_id)) { return $client->external_customer_id; } $client->loadMissing('user'); $name = $client->user?->name ?? $data['name'] ?? 'Cliente'; $email = $client->user?->email ?? $data['email'] ?? null; $document = $this->sanitizeDigits($client->document ?? $data['document'] ?? null); if (empty($email) || empty($document)) { Log::channel('pagarme')->warning( 'Skipping customer creation because the client is missing email or document.', [ 'client_id' => $client->id, 'user_id' => $client->user_id, ] ); return null; } $address = $this->buildAddress($data); $phones = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null); $code = $this->ensureCustomerCode($client); $customerData = PagarmeCustomerResponseData::fromArray($this->pagarmeRequest( method: 'POST', path: '/customers', payload: new PagarmeCustomerRequestData( name: $name, email: $email, document: $document, type: $this->personType($document), documentType: $this->documentType($document), code: $code, address: $address, phones: $phones, ), idempotencyKey: $this->idempotencyKey($client->id), errorMessage: 'Erro ao criar cliente no Pagar.me.', )); $customerId = $customerData->id(); if (! $customerId) { Log::channel('pagarme')->error('Customer creation returned an empty id.', [ 'client_id' => $client->id, 'response' => $customerData->toArray(), ]); throw new \RuntimeException('Customer creation returned an empty id.'); } $client->forceFill([ 'external_customer_id' => $customerId, 'external_customer_code' => $code, ])->save(); return $customerId; } public function updateCustomer(string $customerId, int $clientId, array $data): void { $payload = new PagarmeCustomerUpdateRequestData( name: $data['name'] ?? null, email: $data['email'] ?? null, document: $this->sanitizeDigits($data['document'] ?? null), type: $data['type'] ?? null, documentType: $data['document_type'] ?? null, code: $data['code'] ?? null, address: $this->addressFromPayload($data['address'] ?? null), phones: $this->phonesFromPayload($data['phones'] ?? null), ); if (empty($payload->toArray())) { return; } $this->pagarmeRequest( method: 'PATCH', path: "/customers/{$customerId}", payload: $payload, idempotencyKey: $this->idempotencyKey($clientId, "customer-update-{$customerId}"), errorMessage: 'Erro ao atualizar cliente no Pagar.me.', ); } private function idempotencyKey(int $clientId, string $suffix = 'customer'): string { return "client-{$clientId}-{$suffix}"; } private function ensureCustomerCode(Client $client): string { if ($this->hasUuidCode($client->external_customer_code, 'client')) { return $client->external_customer_code; } $code = 'client-'.(string) Str::uuid(); $client->forceFill(['external_customer_code' => $code])->save(); return $code; } private function hasUuidCode(?string $code, string $prefix): bool { return is_string($code) && preg_match("/^{$prefix}-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i", $code) === 1; } private function buildAddress(array $data): PagarmeCustomerAddressRequestData { $zipCode = $this->sanitizeDigits($data['zip_code'] ?? null); $street = (string) ($data['address'] ?? ''); $number = (string) ($data['number'] ?? '0'); $neighborhood = (string) ($data['district'] ?? ''); $city = (string) ($data['city'] ?? ''); $state = (string) ($data['state'] ?? ''); $country = (string) ($data['country'] ?? 'BR'); $complement = (string) ($data['complement'] ?? ''); $line1Parts = array_filter([$number, $street, $neighborhood], static fn ($value) => $value !== ''); $line1 = implode(', ', $line1Parts); $line2 = $complement ?: (string) ($data['instructions'] ?? ''); return new PagarmeCustomerAddressRequestData( line1: $line1, line2: $line2, zipCode: $zipCode, city: $city, state: $state, country: $country, ); } private function buildPhones(?string $phone): PagarmeCustomerPhonesRequestData { $digits = $this->sanitizeDigits($phone); if (empty($digits)) { return new PagarmeCustomerPhonesRequestData; } $countryCode = '55'; $areaCode = substr($digits, 0, 2); $number = substr($digits, 2); if (strlen($digits) <= 2) { $areaCode = ''; $number = $digits; } return new PagarmeCustomerPhonesRequestData( mobilePhone: new PagarmeCustomerPhoneData( countryCode: $countryCode, areaCode: $areaCode, number: $number, ), ); } private function addressFromPayload(mixed $address): ?PagarmeCustomerAddressRequestData { if (! is_array($address) || empty($address)) { return null; } return new PagarmeCustomerAddressRequestData( line1: $address['line_1'] ?? null, line2: $address['line_2'] ?? null, zipCode: $address['zip_code'] ?? null, city: $address['city'] ?? null, state: $address['state'] ?? null, country: $address['country'] ?? null, ); } private function phonesFromPayload(mixed $phones): ?PagarmeCustomerPhonesRequestData { if (! is_array($phones) || empty($phones)) { return null; } return new PagarmeCustomerPhonesRequestData( homePhone: $this->phoneFromPayload($phones['home_phone'] ?? null), mobilePhone: $this->phoneFromPayload($phones['mobile_phone'] ?? null), ); } private function phoneFromPayload(mixed $phone): ?PagarmeCustomerPhoneData { if (! is_array($phone) || empty($phone)) { return null; } return new PagarmeCustomerPhoneData( countryCode: (string) ($phone['country_code'] ?? ''), areaCode: (string) ($phone['area_code'] ?? ''), number: (string) ($phone['number'] ?? ''), ); } private function documentType(string $document): string { return strlen($document) === 14 ? 'CNPJ' : 'CPF'; } private function filterFilledRecursive(array $data): array { $filtered = []; foreach ($data as $key => $value) { if (is_array($value)) { $value = $this->filterFilledRecursive($value); } if ($value !== null && $value !== '' && $value !== []) { $filtered[$key] = $value; } } return $filtered; } private function personType(string $document): string { return strlen($document) === 14 ? 'company' : 'individual'; } private function sanitizeDigits(?string $value): string { return preg_replace('/\D+/', '', (string) $value) ?? ''; } }