# Blochka API — инструкция для Flutter-разработчика

Документ описывает, как мобильное приложение должно обращаться к API после обновления безопасности.

---

## 1. Общие правила

| Параметр | Значение |
|----------|----------|
| Протокол | только **HTTPS** (`https://ваш-домен.com/api/...`) |
| Метод | **POST** (для `login.php`, `register.php` и остальных эндпоинтов) |
| Ключ приложения | заголовок **`X-Api-Key`** (рекомендуется) **или** поле **`secretkey`** в теле запроса |
| Формат тела | `application/x-www-form-urlencoded` **или** `application/json` |
| Пароль пользователя | передаётся в теле запроса **только по HTTPS**; на сервере хранится хеш (bcrypt) |

> **Важно:** `API_SECRET_KEY` (ключ приложения) — это **не** пароль пользователя.  
> Его выдаёт бэкенд / владелец проекта. Не коммитить в публичный репозиторий.

---

## 2. Зависимости

`pubspec.yaml`:

```yaml
dependencies:
  http: ^1.2.0
```

---

## 3. Базовый HTTP-клиент

Скопируйте и подставьте свои `baseUrl` и `apiKey`:

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class BlochkaApi {
  BlochkaApi({
    required this.baseUrl, // например: https://example.com/api
    required this.apiKey,  // API_SECRET_KEY с сервера
  });

  final String baseUrl;
  final String apiKey;

  Map<String, String> get _headers => {
        'X-Api-Key': apiKey,
        'Accept': 'application/json',
      };

  /// Form-urlencoded (классический POST для PHP)
  Future<http.Response> postForm(
    String path,
    Map<String, String> fields,
  ) {
    return http.post(
      Uri.parse('$baseUrl/$path'),
      headers: {
        ..._headers,
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
      },
      body: fields,
    );
  }

  /// JSON (тоже поддерживается сервером)
  Future<http.Response> postJson(
    String path,
    Map<String, dynamic> body,
  ) {
    return http.post(
      Uri.parse('$baseUrl/$path'),
      headers: {
        ..._headers,
        'Content-Type': 'application/json; charset=utf-8',
      },
      body: jsonEncode(body),
    );
  }
}
```

**Альтернатива заголовку** — ключ в теле запроса:

```dart
fields['secretkey'] = apiKey;
```

---

## 4. Регистрация

### Эндпоинт

```
POST https://ваш-домен.com/api/register.php
```

### Поля запроса

| Поле | Обязательно | Описание |
|------|:-----------:|----------|
| `firstname` | да | Имя |
| `lastname` | нет | Фамилия |
| `phone` | да | Телефон, например `380931234567` |
| `password` | да | Пароль пользователя |

### Успешный ответ (HTTP 200)

```json
{
  "success": true,
  "user_id": 123
}
```

### Ошибки

```json
{ "success": false, "error": "Phone already registered" }
```

```json
{ "success": false, "error": "Missing required fields" }
```

### Пример кода (Dart)

```dart
class RegisterResult {
  final bool success;
  final int? userId;
  final String? error;

  RegisterResult({required this.success, this.userId, this.error});

  factory RegisterResult.fromJson(Map<String, dynamic> json) {
    return RegisterResult(
      success: json['success'] == true,
      userId: json['user_id'] as int?,
      error: json['error'] as String?,
    );
  }
}

Future<RegisterResult> register({
  required String firstname,
  String lastname = '',
  required String phone,
  required String password,
}) async {
  final api = BlochkaApi(
    baseUrl: 'https://ваш-домен.com/api',
    apiKey: 'ВАШ_API_SECRET_KEY',
  );

  final response = await api.postForm('register.php', {
    'firstname': firstname,
    'lastname': lastname,
    'phone': phone,
    'password': password,
  });

  final data =
      jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>;

  if (response.statusCode == 401) {
    return RegisterResult(
      success: false,
      error: 'Неверный ключ приложения (X-Api-Key)',
    );
  }

  return RegisterResult.fromJson(data);
}
```

После успешной регистрации обычно сразу вызывают **login**, чтобы получить данные профиля.

---

## 5. Авторизация (вход)

### Эндпоинт

```
POST https://ваш-домен.com/api/login.php
```

### Поля запроса

| Поле | Обязательно | Описание |
|------|:-----------:|----------|
| `phone` | да | Телефон |
| `password` | да | Пароль |

### Успешный ответ (HTTP 200)

Поле **`password` в ответе отсутствует** — это сделано намеренно.

```json
{
  "id": 1,
  "user_avatar": "",
  "firstname": "Іван",
  "lastname": "Петренко",
  "phone_number": "380931234567",
  "date_of_birth": "",
  "balance": "0",
  "rank": null,
  "cashback": null,
  "delivery_service": "",
  "delivery_adress": "",
  "firebase_token": "",
  "success": true,
  "score": 33
}
```

### Неверный телефон или пароль

```json
{ "success": false }
```

### Пример кода (Dart)

```dart
class UserModel {
  final int id;
  final String firstname;
  final String lastname;
  final String phoneNumber;
  final String balance;
  final String deliveryService;
  final String deliveryAddress;
  final int score;

  UserModel({
    required this.id,
    required this.firstname,
    required this.lastname,
    required this.phoneNumber,
    required this.balance,
    required this.deliveryService,
    required this.deliveryAddress,
    required this.score,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) {
    if (json['success'] != true) {
      throw const FormatException('Login failed');
    }
    return UserModel(
      id: json['id'] as int,
      firstname: json['firstname']?.toString() ?? '',
      lastname: json['lastname']?.toString() ?? '',
      phoneNumber: json['phone_number']?.toString() ?? '',
      balance: json['balance']?.toString() ?? '0',
      deliveryService: json['delivery_service']?.toString() ?? '',
      deliveryAddress: json['delivery_adress']?.toString() ?? '',
      score: json['score'] is int
          ? json['score'] as int
          : int.tryParse('${json['score']}') ?? 0,
    );
  }
}

Future<UserModel?> login({
  required String phone,
  required String password,
}) async {
  final api = BlochkaApi(
    baseUrl: 'https://ваш-домен.com/api',
    apiKey: 'ВАШ_API_SECRET_KEY',
  );

  final response = await api.postForm('login.php', {
    'phone': phone,
    'password': password,
  });

  if (response.statusCode == 401) {
    throw Exception('Unauthorized: проверьте X-Api-Key');
  }

  final data =
      jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>;

  if (data['success'] != true) {
    return null;
  }

  return UserModel.fromJson(data);
}
```

### Вход через JSON (опционально)

```dart
final response = await api.postJson('login.php', {
  'phone': phone,
  'password': password,
});
```

---

## 6. Пример экрана: регистрация → вход

```dart
Future<void> onRegisterPressed() async {
  final result = await register(
    firstname: firstNameController.text.trim(),
    lastname: lastNameController.text.trim(),
    phone: normalizePhone(phoneController.text),
    password: passwordController.text,
  );

  if (!result.success) {
    showError(result.error ?? 'Ошибка регистрации');
    return;
  }

  await onLoginPressed();
}

Future<void> onLoginPressed() async {
  final user = await login(
    phone: normalizePhone(phoneController.text),
    password: passwordController.text,
  );

  if (user == null) {
    showError('Неверный телефон или пароль');
    return;
  }

  // Сохранить сессию локально.
  // API не возвращает password — храните его сами, если нужен для повторных запросов,
  // лучше через flutter_secure_storage.

  // await secureStorage.write(key: 'user_id', value: '${user.id}');
  // await secureStorage.write(key: 'phone', value: user.phoneNumber);

  Navigator.pushReplacementNamed(context, '/home');
}

String normalizePhone(String raw) {
  final digits = raw.replaceAll(RegExp(r'\D'), '');
  if (digits.startsWith('0') && digits.length == 10) {
    return '38$digits';
  }
  if (digits.length == 9) {
    return '380$digits';
  }
  return digits;
}
```

---

## 7. HTTP-коды ответов

| Код | Значение |
|-----|----------|
| **200** | Запрос обработан (смотрите поле `success` в JSON) |
| **400** | Не переданы обязательные поля |
| **401** | Неверный `X-Api-Key` / `secretkey` |
| **405** | Использован не POST (например GET) |

---

## 8. Остальные эндпоинты

Для всех остальных скриптов (`get_products.php`, `get_orders.php`, `create_order.php` и т.д.) действуют **те же правила**:

1. **HTTPS**
2. **POST**
3. Заголовок **`X-Api-Key`** или поле **`secretkey`**
4. Тело: form или JSON

### Эндпоинты, которые изменили метод

| Файл | Было | Стало |
|------|------|-------|
| `get_banners.php` | GET + `secretkey` в URL | **POST** + ключ в теле/заголовке |
| `novapost_stocks.php` | GET `?city=` | **POST** `city` |
| `ukraine_cities.php` | GET без ключа | **POST** + ключ |

---

## 9. Чеклист перед релизом

- [ ] Базовый URL начинается с `https://`
- [ ] На каждый запрос отправляется `X-Api-Key` (или `secretkey`)
- [ ] `login.php` / `register.php` вызываются только методом **POST**
- [ ] После логина **не ожидается** поле `password` в JSON-ответе
- [ ] Ключ приложения не захардкожен в открытом виде в публичном репозитории
- [ ] Пароль пользователя при необходимости хранится в `flutter_secure_storage`

---

## 10. Контакты / настройка

- **Base URL** и **API_SECRET_KEY** — получить у владельца проекта / бэкенда.
- Для локальной отладки без SSL на сервере бэкенд может временно включить `$API_ALLOW_HTTP_LOCAL = true` в `config.local.php` (только dev).

---

*Версия документа: июнь 2026 — после обновления безопасности API.*
