recipient_id)) { return $provider->recipient_id; } $bankAccountData = $data['recipient_default_bank_account']; $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']); $metadata = $data['recipient_metadata'] ?? []; $paymentMode = $data['recipient_payment_mode']; $recipientType = $data['recipient_type'] ?? 'individual'; $addressParts = $this->extractAddressParts($data); $monthlyIncome = isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000; $occupation = $data['professional_occupation'] ?? 'autonomo'; $payload = $this->filterFilledRecursive([ 'code' => $this->buildRecipientCode($provider, $data), 'register_information' => [ 'name' => $data['recipient_name'], 'email' => $data['recipient_email'], 'document' => preg_replace('/\D+/', '', $data['recipient_document']), 'type' => $recipientType, 'birthdate' => $this->formatBirthdate($data['birth_date'] ?? null), 'monthly_income' => $monthlyIncome, 'professional_occupation' => $occupation, 'phone_numbers' => $this->buildPhoneNumbers($data['phone'] ?? null), 'address' => [ 'street' => $data['address'], 'complementary' => $addressParts['complementary'], 'street_number' => $addressParts['street_number'], 'neighborhood' => $addressParts['neighborhood'], 'city' => $data['city'] ?? null, 'state' => $data['state'] ?? null, 'zip_code' => preg_replace('/\D+/', '', $data['zip_code']), 'reference_point' => $addressParts['reference_point'], ], ], 'default_bank_account' => [ 'holder_name' => $bankAccountData['holder_name'], 'holder_type' => $bankAccountData['holder_type'], 'holder_document' => preg_replace('/\D+/', '', $bankAccountData['holder_document']), 'bank' => $bankAccountData['bank'], 'branch_number' => $bankAccountData['branch_number'], 'branch_check_digit' => $bankAccountData['branch_check_digit'] ?? null, 'account_number' => $bankAccountData['account_number'], 'account_check_digit' => $bankAccountData['account_check_digit'], 'type' => $bankAccountData['type'], ], 'transfer_settings' => [ 'transfer_enabled' => false, 'transfer_interval' => 'Daily', 'transfer_day' => 0, ], 'automatic_anticipation_settings' => [ 'enabled' => false, ], ]); $endpoint = $this->pagarmeUrl('/recipients'); PagarmeHttpLogger::logRequest('POST', $endpoint, $payload); $response = $this->pagarmeRequest($provider->id) ->post($endpoint, $payload); if ($response->failed()) { Log::channel('pagarme')->error('Pagar.me recipient creation failed', [ 'status' => $response->status(), 'body' => $response->json() ?? $response->body(), 'payload' => $payload, ]); throw new \RuntimeException('Erro ao criar recebedor no Pagar.me.'); } $recipientData = $response->json(); $recipientId = $recipientData['id'] ?? null; if (! $recipientId) { Log::channel('pagarme')->error('Pagar.me recipient creation returned empty id', [ 'response' => $recipientData, ]); throw new \RuntimeException('Pagar.me recipient creation returned an empty id.'); } $provider->forceFill([ 'recipient_id' => $recipientId, 'recipient_name' => $data['recipient_name'], 'recipient_email' => $data['recipient_email'], 'recipient_description' => $data['recipient_description'], 'recipient_document' => $data['recipient_document'], 'recipient_type' => $recipientType, 'recipient_code' => $data['recipient_code'], 'recipient_payment_mode' => $paymentMode, 'recipient_default_bank_account' => $bankAccountData, 'recipient_transfer_settings' => [ 'transfer_enabled' => false, 'transfer_interval' => 'daily', 'transfer_day' => 0, ], 'recipient_automatic_anticipation_settings' => [ 'enabled' => false, ], 'recipient_metadata' => $metadata, ])->save(); return $recipientId; } public function updateDefaultBankAccount(Provider $provider, array $bankAccountData): Provider { if (empty($provider->recipient_id)) { throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para atualizar a conta bancaria.'); } $bankAccountData = $this->normalizeBankAccountPayload($bankAccountData); $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']); $payload = [ 'bank_account' => $this->filterFilledRecursive($bankAccountData), ]; $endpoint = $this->pagarmeUrl("/recipients/{$provider->recipient_id}/default-bank-account"); PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload); $response = $this->pagarmeRequest($provider->id, 'default-bank-account-'.sha1(json_encode($payload))) ->patch($endpoint, $payload); if ($response->failed()) { Log::channel('pagarme')->error('Pagar.me recipient bank account update failed', [ 'provider_id' => $provider->id, 'recipient_id' => $provider->recipient_id, 'status' => $response->status(), 'body' => $response->json() ?? $response->body(), 'payload' => $payload, ]); throw new \RuntimeException('Erro ao atualizar conta bancaria do recebedor no Pagar.me.'); } $recipientData = $response->json(); $provider->forceFill([ 'recipient_default_bank_account' => $recipientData['default_bank_account'] ?? $bankAccountData, ])->save(); return $provider->fresh(); } // private function idempotencyKey(int $providerId, string $suffix = 'recipient'): string { return "provider-{$providerId}-{$suffix}"; } private function pagarmeRequest(int $providerId, string $suffix = 'recipient') { $secretKey = config('services.pagarme.secret_key'); if (empty($secretKey)) { Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.'); throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.'); } return Http::withBasicAuth($secretKey, '') ->withHeaders([ 'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix), 'Content-Type' => 'application/json', 'Accept' => 'application/json', ]); } private function pagarmeUrl(string $path): string { return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/'); } // private function buildPhoneNumbers(?string $phone): array { $digits = preg_replace('/\D+/', '', (string) $phone) ?? ''; if (strlen($digits) < 10) { return [[ 'ddd' => '11', 'number' => '999999999', 'type' => 'mobile', ]]; } if (str_starts_with($digits, '55')) { $digits = substr($digits, 2); } $areaCode = substr($digits, 0, 2); $number = substr($digits, 2); return [[ 'ddd' => $areaCode, 'number' => $number, 'type' => 'mobile', ]]; } private function buildRecipientCode(Provider $provider, array $data): string { $baseCode = preg_replace('/\D+/', '', (string) ($data['recipient_code'] ?? '')) ?: (string) $provider->id; // Pagar.me exige external_id unico; usar code deterministico por provider evita colisao entre contas. return Str::limit("provider-{$provider->id}-{$baseCode}", 52, ''); } private function extractAddressParts(array $data): array { $addressLine = trim((string) ($data['address'] ?? '')); $segments = array_map('trim', explode(',', $addressLine)); $streetSegment = $segments[0] ?? ''; $streetNumber = $data['number'] ?? null; $neighborhood = $data['district'] ?? null; $referencePoint = $data['reference_point'] ?? null; $complementary = $data['complement'] ?? null; if ($streetNumber === null) { preg_match('/^(\d+)/', $streetSegment, $matches); $streetNumber = $matches[1] ?? 'S/N'; } if ($neighborhood === null) { $neighborhood = $segments[1] ?? 'N/A'; } if ($referencePoint === null) { $referencePoint = 'N/A'; } if ($complementary === null) { $complementary = 'N/A'; } return [ 'street_number' => (string) $streetNumber, 'neighborhood' => (string) $neighborhood, 'reference_point' => (string) $referencePoint, 'complementary' => (string) $complementary, ]; } 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 formatBirthdate(mixed $birthdate): ?string { if ($birthdate === null || $birthdate === '') { return null; } if ($birthdate instanceof \DateTimeInterface) { return Carbon::instance($birthdate)->format('d/m/Y'); } $birthdate = trim((string) $birthdate); if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $birthdate) === 1) { return $birthdate; } if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthdate) === 1) { return Carbon::createFromFormat('Y-m-d', $birthdate)->format('d/m/Y'); } return Carbon::parse($birthdate)->format('d/m/Y'); } private function normalizeBankAccountHolderName(string $holderName): string { $holderName = trim(preg_replace('/\s+/', ' ', $holderName) ?? ''); if (Str::length($holderName) < 30) { return $holderName; } $parts = explode(' ', $holderName); if (count($parts) >= 3) { $firstName = array_shift($parts); $lastName = array_pop($parts); $initials = array_map( static fn (string $part): string => Str::upper(Str::substr($part, 0, 1)), $parts ); $abbreviated = trim($firstName.' '.implode(' ', $initials).' '.$lastName); if (Str::length($abbreviated) < 30) { return $abbreviated; } $firstAndLast = trim($firstName.' '.$lastName); if (Str::length($firstAndLast) < 30) { return $firstAndLast; } } return Str::limit($holderName, 29, ''); } private function normalizeBankAccountPayload(array $bankAccountData): array { return [ 'holder_name' => $bankAccountData['holder_name'], 'holder_type' => $bankAccountData['holder_type'], 'holder_document' => preg_replace('/\D+/', '', $bankAccountData['holder_document']), 'bank' => $bankAccountData['bank'], 'branch_number' => $bankAccountData['branch_number'], 'branch_check_digit' => $bankAccountData['branch_check_digit'] ?? null, 'account_number' => $bankAccountData['account_number'], 'account_check_digit' => $bankAccountData['account_check_digit'], 'type' => $bankAccountData['type'], 'metadata' => $bankAccountData['metadata'] ?? null, ]; } }