KwadMarket Docs
Product Roadmap

Payments & Escrow

Stripe Connect escrow — data model, flow and platform fee strategy

Priority: heavier bet — after traction

This is what enables taking a commission (standard C2C monetization), but carries significant compliance work. Prerequisite: company registration.

Why Stripe Connect

The only reasonable choice for a C2C marketplace with escrow:

  • Built-in escrow: funds are held until delivery is confirmed
  • Handles seller onboarding (KYC/identity verification required by law)
  • Supports refunds, disputes, and chargebacks natively
  • Handles VAT/tax reporting; platform fee collection is built-in

Data model

model StripeAccount {
  id                 Int      @id @default(autoincrement())
  userId             String   @unique
  user               User     @relation(fields: [userId], references: [id])
  stripeAccountId    String   @unique  // acct_xxx
  onboardingComplete Boolean  @default(false)
  createdAt          DateTime @default(now())
}

model Transaction {
  id                  Int      @id @default(autoincrement())
  dealId              Int      @unique
  deal                Deal     @relation(fields: [dealId], references: [id])
  buyerId             String
  buyer               User     @relation("buyer", fields: [buyerId], references: [id])
  sellerId            String
  seller              User     @relation("seller", fields: [sellerId], references: [id])
  stripePaymentIntent String   @unique  // pi_xxx
  amount              Decimal
  currency            String
  platformFee         Decimal  // our commission
  status              TransactionStatus
  paidAt              DateTime?
  releasedAt          DateTime?  // funds released to seller
  refundedAt          DateTime?
  createdAt           DateTime @default(now())
  updatedAt           DateTime @updatedAt
}

enum TransactionStatus {
  PENDING          // payment intent created
  PAID             // buyer paid, funds held in escrow
  DELIVERY_PENDING // awaiting shipping
  DELIVERED        // tracking confirms delivery
  COMPLETED        // funds released to seller
  DISPUTED         // buyer opened dispute
  REFUNDED         // full or partial refund
  CANCELLED        // cancelled before payment
}

Flow

Seller onboarding. Seller clicks "Start selling" → redirected to Stripe Connect onboarding (Stripe handles KYC: ID, bank account, address). Webhook confirms onboarding → onboardingComplete = true. Seller can't publish deals until onboarding is complete.

Buyer purchases. Backend creates a PaymentIntent with transfer_data.destination = seller's account, application_fee_amount = platform commission, capture_method = manual (hold funds). Buyer pays via Stripe Elements. Webhook payment_intent.succeeded → status PAID.

Escrow period. Funds held by Stripe. Seller ships → enters tracking number; the system monitors tracking via carrier API (delivery spec). On delivery: status DELIVERED.

Release or dispute. After delivery + 48h grace period: auto-capture → funds to seller minus fee. Buyer disputes → status DISPUTEDadmin review. Refund full/partial via the Stripe Refund API.

Tasks

Backend: StripeModule; Connect onboarding endpoint (account link) + account.updated webhook; PaymentIntent endpoint (escrow hold) + payment webhooks; capture endpoint (release funds); refund endpoint; transaction REST endpoints (status, history); cron auto-release after 48h post-delivery if no dispute; guard preventing deal publishing without completed onboarding.

Frontend: Stripe Elements payment form; seller onboarding flow (redirect + return page); "Buy Now" on deal page; transaction status page (buyer + seller views); payment history in profile; seller dashboard (earnings, pending payouts).

Platform fee strategy

Decision: flat 5% charged to the buyer on top of the deal price.

Deal price €100 → buyer pays €105 → seller receives €100 → platform keeps €5. Transparent and seller-friendly (the seller always gets what they listed).

On this page