Розробка мобільного додатку для фриланс-біржі
Фриланс-біржа—двостороння з escrow-платежами, чатом, системою ставок на проекти та репутаційними механізмами. Технічно складніше більшості eCommerce-додатків: два типи користувачів з принципово різними інтерфейсами, escrow як фінансовий інструмент та арбітраж споргів.
Архітектура двустороннього маркетплейсу
Замовники та виконавці—різні ролі в одному акаунті або повністю різні сутності, залежить від концепції. Два підходи:
Раздільні акаунти—як 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()
]
}
}
}
Escrow: захищені платежі
Головна фіча біржі—гроші зберігаються у платформи до виконання завдання. Схема:
- Замовник створює проект та вносить суму на escrow-рахунок (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): великий проект ділиться на етапи, кожен з окремим escrow. Виконавець отримує гроші по мірі прийняття етапів—знижує ризик для обох сторін.
Система ставок (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 тижнів |
| Escrow-платежі, Stripe Connect для виплат | +3–4 тижні |
| Чат з WebSocket та offline-queue | +2–3 тижні |
| KYC-інтеграція, система відзивів | +2 тижні |
Вартість розраховується індивідуально після вибору стеку.







