Реализация импорта контактов (Google/Outlook) на сайте
Импорт контактов из Google или Outlook на сайт — типичная задача для CRM-систем, HR-порталов, платформ нетворкинга. Пользователь авторизуется через OAuth, даёт разрешение на чтение контактов, сайт забирает список и предлагает выбрать, кого пригласить или синхронизировать. Технически Google и Outlook — принципиально разные API, объединённые одним UI-флоу.
Google Contacts API: настройка OAuth
В Google Cloud Console: создать проект → включить «People API» → создать OAuth 2.0 Client ID (тип: Web application) → добавить redirect URI.
Нужные скоупы:
-
https://www.googleapis.com/auth/contacts.readonly— чтение контактов -
https://www.googleapis.com/auth/contacts.other.readonly— контакты из «Другие контакты»
use Google\Client as GoogleClient;
class GoogleContactsService
{
private GoogleClient $client;
public function __construct()
{
$this->client = new GoogleClient();
$this->client->setClientId(config('services.google.client_id'));
$this->client->setClientSecret(config('services.google.client_secret'));
$this->client->setRedirectUri(config('services.google.redirect'));
$this->client->addScope('https://www.googleapis.com/auth/contacts.readonly');
$this->client->setAccessType('offline'); // получаем refresh_token
}
public function getAuthUrl(): string
{
return $this->client->createAuthUrl();
}
public function handleCallback(string $code): array
{
$token = $this->client->fetchAccessTokenWithAuthCode($code);
// Сохраняем токен для пользователя
return $token;
}
}
Получение контактов из Google People API
public function getContacts(array $accessToken): array
{
$this->client->setAccessToken($accessToken);
if ($this->client->isAccessTokenExpired() && isset($accessToken['refresh_token'])) {
$this->client->fetchAccessTokenWithRefreshToken($accessToken['refresh_token']);
}
$service = new \Google\Service\PeopleService($this->client);
$contacts = [];
$pageToken = null;
do {
$params = [
'personFields' => 'names,emailAddresses,phoneNumbers',
'pageSize' => 1000,
];
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$result = $service->people_connections->listPeopleConnections('people/me', $params);
foreach ($result->getConnections() ?? [] as $person) {
$name = $person->getNames()[0] ?? null;
$email = $person->getEmailAddresses()[0] ?? null;
$phone = $person->getPhoneNumbers()[0] ?? null;
if (!$email) continue; // пропускаем без email
$contacts[] = [
'name' => $name?->getDisplayName() ?? '',
'email' => $email->getValue(),
'phone' => $phone?->getValue() ?? '',
];
}
$pageToken = $result->getNextPageToken();
} while ($pageToken);
return $contacts;
}
Пагинация обязательна: у пользователя может быть 5000+ контактов, API возвращает максимум 1000 за запрос.
Microsoft Graph API: Outlook/Office 365 контакты
Регистрация приложения в Azure AD → «App registrations» → «New registration». Нужные разрешения: Contacts.Read (Delegated).
use Microsoft\Graph\Graph;
use Microsoft\Graph\Model\Contact;
class OutlookContactsService
{
public function getAuthUrl(): string
{
$params = http_build_query([
'client_id' => config('services.microsoft.client_id'),
'response_type' => 'code',
'redirect_uri' => config('services.microsoft.redirect'),
'scope' => 'offline_access Contacts.Read',
'response_mode' => 'query',
]);
return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?{$params}";
}
public function getToken(string $code): array
{
$response = Http::asForm()->post(
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
[
'client_id' => config('services.microsoft.client_id'),
'client_secret' => config('services.microsoft.client_secret'),
'code' => $code,
'redirect_uri' => config('services.microsoft.redirect'),
'grant_type' => 'authorization_code',
]
);
return $response->json();
}
public function getContacts(string $accessToken): array
{
$graph = new Graph();
$graph->setAccessToken($accessToken);
$contacts = [];
$url = '/me/contacts?$select=displayName,emailAddresses,mobilePhone&$top=100';
do {
$result = $graph->createRequest('GET', $url)->execute();
$data = $result->getBody();
foreach ($data['value'] as $contact) {
$email = $contact['emailAddresses'][0]['address'] ?? null;
if (!$email) continue;
$contacts[] = [
'name' => $contact['displayName'] ?? '',
'email' => $email,
'phone' => $contact['mobilePhone'] ?? '',
];
}
$url = $data['@odata.nextLink'] ?? null;
// Убираем базовый URL для Graph SDK
if ($url) {
$url = str_replace('https://graph.microsoft.com/v1.0', '', $url);
}
} while ($url);
return $contacts;
}
}
UI: выбор контактов для импорта
После получения списка пользователь выбирает, каких контактов импортировать:
function ContactImportModal({ contacts, onImport }) {
const [selected, setSelected] = useState(new Set());
const toggle = (email) => {
setSelected(prev => {
const next = new Set(prev);
next.has(email) ? next.delete(email) : next.add(email);
return next;
});
};
return (
<div>
<div className="actions">
<button onClick={() => setSelected(new Set(contacts.map(c => c.email)))}>
Выбрать все ({contacts.length})
</button>
</div>
<ul>
{contacts.map(contact => (
<li key={contact.email}>
<label>
<input
type="checkbox"
checked={selected.has(contact.email)}
onChange={() => toggle(contact.email)}
/>
{contact.name} — {contact.email}
</label>
</li>
))}
</ul>
<button onClick={() => onImport([...selected])}>
Импортировать выбранных ({selected.size})
</button>
</div>
);
}
Хранение токенов
Токены доступа нельзя хранить в сессии — они должны быть в БД, зашифрованы:
// Миграция
$table->text('google_access_token')->nullable();
$table->text('google_refresh_token')->nullable();
$table->timestamp('google_token_expires_at')->nullable();
// В модели User — автоматическое шифрование
protected $casts = [
'google_access_token' => 'encrypted',
'google_refresh_token' => 'encrypted',
];
Сроки
Импорт из одного провайдера (Google или Outlook) с UI выбора контактов, OAuth-флоу и сохранением в базу: 2–3 рабочих дня. Оба провайдера одновременно с обновлением токенов и повторной синхронизацией: 4–5 рабочих дней.







