Разработка мобильного приложения для фриланс-биржи
Фриланс-биржа — это двусторонний маркетплейс с эскроу-платежами, чатом, системой ставок на проекты и репутационными механизмами. Технически сложнее большинства eCommerce-приложений: здесь есть два типа пользователей с принципиально разными интерфейсами, эскроу как финансовый инструмент и арбитраж споров.
Архитектура двустороннего маркетплейса
Заказчики и исполнители — разные роли в одном аккаунте или полностью разные сущности, зависит от концепции. Два подхода:
Раздельные аккаунты — как Upwork: «Нанять» (клиент) и «Найти работу» (фрилансер). Проще в управлении правами, но пользователь не может быть и тем, и другим.
Единый аккаунт с переключением ролей — как Kwork: один профиль, переключение контекста. Технически: userRole: 'client' | 'freelancer' в сессии, разные навигационные деревья.
// iOS: навигация по роли через enum
enum UserContext {
case client
case freelancer
}
class AppCoordinator {
var currentContext: UserContext = .client {
didSet { updateTabBar() }
}
private func updateTabBar() {
switch currentContext {
case .client:
tabBarController.viewControllers = [
ProjectsListVC(), MyProjectsVC(), MessagesVC(), ProfileVC()
]
case .freelancer:
tabBarController.viewControllers = [
FindProjectsVC(), MyOrdersVC(), MessagesVC(), ProfileVC()
]
}
}
}
Эскроу: защищённые платежи
Главная фича биржи — деньги хранятся у платформы до выполнения задания. Схема:
- Заказчик создаёт проект и вносит сумму на эскроу-счёт (charge/capture)
- Исполнитель выполняет работу, сдаёт результат
- Заказчик принимает → платформа переводит средства исполнителю минус комиссию
- Если спор → арбитраж (ручной или автоматический)
- Если заказчик не отвечает N дней → автоматическое подтверждение
# Stripe Connect: split payment с автоматической комиссией платформы
import stripe
def release_payment_to_freelancer(escrow_payment: EscrowPayment) -> None:
platform_fee = int(escrow_payment.amount * Decimal("0.10")) # 10% комиссия
# Создаём трансфер исполнителю
transfer = stripe.Transfer.create(
amount=escrow_payment.amount - platform_fee,
currency="usd",
destination=escrow_payment.freelancer_stripe_account_id,
transfer_group=escrow_payment.project_id,
metadata={
"project_id": escrow_payment.project_id,
"order_id": escrow_payment.order_id
}
)
db.update_escrow(
escrow_payment.id,
status="RELEASED",
transfer_id=transfer.id
)
notify_freelancer(escrow_payment.freelancer_id, "payment_released", transfer.amount)
Частичная оплата (milestone payments): большой проект делится на этапы, каждый с отдельным эскроу. Исполнитель получает деньги по мере принятия этапов — снижает риск для обеих сторон.
Система ставок (bidding)
Исполнители делают ставки на проекты: предлагают цену и срок. Заказчик видит список предложений, портфолио кандидатов, их рейтинги.
// Android: список ставок с сортировкой
data class Bid(
val id: String,
val freelancerId: String,
val amount: Decimal,
val deliveryDays: Int,
val coverLetter: String,
val freelancer: FreelancerProfile
)
@Composable
fun BidsList(
bids: List<Bid>,
sortBy: BidSortOption,
onHire: (Bid) -> Unit
) {
val sorted = when (sortBy) {
BidSortOption.PRICE -> bids.sortedBy { it.amount }
BidSortOption.RATING -> bids.sortedByDescending { it.freelancer.rating }
BidSortOption.DELIVERY -> bids.sortedBy { it.deliveryDays }
}
LazyColumn {
items(sorted, key = { it.id }) { bid ->
BidCard(bid = bid, onHire = { onHire(bid) })
}
}
}
Ограничение ставок: начинающие фрилансеры получают N бесплатных ставок в месяц, после — платные. Это стандартная монетизационная модель бирж.
Встроенный чат
Чат между заказчиком и исполнителем — обязательный компонент. Требования: доставка сообщений, статусы (отправлено/прочитано), файлы и изображения, ссылки на задание в контексте разговора.
Технически: WebSocket для реального времени + offline queue для отправки без интернета. Хранилище сообщений — Core Data (iOS) или Room (Android) для оффлайн-доступа.
// iOS: оффлайн-очередь сообщений
class MessageQueue {
private let context: NSManagedObjectContext
private var syncTimer: Timer?
func sendMessage(_ content: String, conversationId: String) {
// Сохраняем локально немедленно
let pending = PendingMessage(context: context)
pending.id = UUID().uuidString
pending.content = content
pending.conversationId = conversationId
pending.createdAt = Date()
pending.status = "PENDING"
try? context.save()
// Показываем в UI как отправленное
NotificationCenter.default.post(name: .messageSent, object: pending)
// Синхронизируем при наличии соединения
syncPendingMessages()
}
func syncPendingMessages() {
guard networkMonitor.isConnected else { return }
let pending = fetchPendingMessages()
pending.forEach { msg in
apiClient.sendMessage(msg) { result in
switch result {
case .success(let serverMessage):
msg.status = "DELIVERED"
msg.serverId = serverMessage.id
try? context.save()
case .failure:
// Повторим при следующей синхронизации
break
}
}
}
}
}
Рейтинги и репутация
После закрытия заказа — взаимные отзывы. Рейтинг влияет на ранжирование в поиске. Защита от накрутки:
- Отзыв можно оставить только для конкретного завершённого заказа
- Взаимная анонимность до публикации (как на Upwork) — оба видят отзыв только после того, как оба написали
- Алгоритм Bayesian average вместо простого среднего — новый профиль без отзывов не показывает «5.0 из 5»
Верификация и KYC
Для вывода средств — обязательная верификация личности. На биржах часто два уровня:
Базовый — подтверждение email + телефона. Позволяет работать, но лимит на вывод.
Расширенный — KYC через SDK (Sumsub, Veriff). Снимает лимиты, даёт бейдж «Верифицирован».
Ориентиры по срокам
| Scope | Срок |
|---|---|
| Каталог проектов, профили, ставки, нет платежей | 5–7 недель |
| Эскроу-платежи, Stripe Connect для выплат | +3–4 недели |
| Чат с WebSocket и offline-queue | +2–3 недели |
| KYC-интеграция, система отзывов | +2 недели |
Стоимость рассчитывается индивидуально после анализа требований и выбранного стека.







