Crowdfunding Platform Development

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Crowdfunding Platform Development

Crowdfunding platform is not just "site with payment page". It's campaign management system, funds collection, creator payouts, backer communication and compliance with payment law. Difference between working product and feature list is in implementation details of each part.

Financing Models

Before designing data schema, determine model:

All-or-Nothing (AON) — funds debited only on goal achievement. Kickstarter model. Technically harder: need pre-authorization (hold) or delayed capture.

Keep-it-All (KIA) — funds debited immediately, creator gets everything regardless of goal. Indiegogo model. Simpler implement, but legally requires clear refund terms.

Hybrid — fixed goal, stretch goals open on exceeding. Most complex with campaign state logic.

Data Schema

CREATE TABLE campaigns (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    creator_id  UUID NOT NULL REFERENCES users(id),
    title       VARCHAR(200) NOT NULL,
    slug        VARCHAR(200) UNIQUE NOT NULL,
    description TEXT,
    goal_amount NUMERIC(15,2) NOT NULL,
    currency    CHAR(3) NOT NULL DEFAULT 'RUB',
    model       VARCHAR(20) NOT NULL CHECK (model IN ('aon','kia','hybrid')),
    status      VARCHAR(20) NOT NULL DEFAULT 'draft'
                CHECK (status IN ('draft','active','funded','failed','cancelled')),
    starts_at   TIMESTAMPTZ NOT NULL,
    ends_at     TIMESTAMPTZ NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE pledges (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    campaign_id     UUID NOT NULL REFERENCES campaigns(id),
    backer_id       UUID NOT NULL REFERENCES users(id),
    amount          NUMERIC(15,2) NOT NULL,
    reward_id       UUID REFERENCES rewards(id),
    status          VARCHAR(20) NOT NULL DEFAULT 'pending'
                    CHECK (status IN ('pending','authorized','captured','refunded','failed')),
    payment_intent  VARCHAR(200),   -- Stripe PaymentIntent or YooKassa payment_id
    captured_at     TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE rewards (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    campaign_id UUID NOT NULL REFERENCES campaigns(id),
    title       VARCHAR(200) NOT NULL,
    description TEXT,
    min_pledge  NUMERIC(15,2) NOT NULL,
    limit_qty   INTEGER,            -- NULL = unlimited
    claimed_qty INTEGER NOT NULL DEFAULT 0,
    ships_at    DATE,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_pledges_campaign_status
    ON pledges(campaign_id, status)
    WHERE status IN ('authorized','captured');

Payment Flow for AON Model

AON requires two-stage payment: authorize (hold) first, then capture on campaign success.

Stripe:

import stripe

stripe.api_key = settings.STRIPE_SECRET_KEY

def authorize_pledge(pledge, card_token):
    intent = stripe.PaymentIntent.create(
        amount=int(pledge.amount * 100),
        currency=pledge.campaign.currency.lower(),
        payment_method=card_token,
        capture_method='manual',
        confirm=True,
        metadata={
            'pledge_id': str(pledge.id),
            'campaign_id': str(pledge.campaign_id),
        }
    )
    pledge.payment_intent = intent.id
    pledge.status = 'authorized'
    pledge.save()
    return intent


def capture_pledges_for_campaign(campaign_id):
    """Called on campaign success"""
    pledges = Pledge.objects.filter(
        campaign_id=campaign_id,
        status='authorized'
    )
    for pledge in pledges:
        try:
            stripe.PaymentIntent.capture(pledge.payment_intent)
            pledge.status = 'captured'
            pledge.captured_at = timezone.now()
            pledge.save()
        except stripe.error.InvalidRequestError as e:
            pledge.status = 'failed'
            pledge.save()
            logger.error(f'Capture failed for pledge {pledge.id}: {e}')


def cancel_pledges_for_campaign(campaign_id):
    """Called on campaign failure"""
    pledges = Pledge.objects.filter(
        campaign_id=campaign_id,
        status='authorized'
    )
    for pledge in pledges:
        stripe.PaymentIntent.cancel(pledge.payment_intent)
        pledge.status = 'refunded'
        pledge.save()

Important: Stripe holds authorization max 7 days for cards (up to 30 for some types). For campaigns longer than 7 days need different strategy — e.g. save payment method and capture on last day.

Saving Card for Long Campaigns

def save_payment_method(user, card_token):
    if not user.stripe_customer_id:
        customer = stripe.Customer.create(
            email=user.email,
            metadata={'user_id': str(user.id)}
        )
        user.stripe_customer_id = customer.id
        user.save()

    setup_intent = stripe.SetupIntent.create(
        customer=user.stripe_customer_id,
        payment_method=card_token,
        confirm=True,
        usage='off_session',
    )
    return setup_intent.payment_method


def charge_saved_card(pledge):
    intent = stripe.PaymentIntent.create(
        amount=int(pledge.amount * 100),
        currency='rub',
        customer=pledge.backer.stripe_customer_id,
        payment_method=pledge.payment_method_id,
        confirm=True,
        off_session=True,
        metadata={'pledge_id': str(pledge.id)},
    )
    return intent

Celery Tasks for Campaign Completion

from celery import shared_task
from django.utils import timezone


@shared_task
def check_campaign_deadline(campaign_id):
    campaign = Campaign.objects.get(id=campaign_id)

    if campaign.ends_at > timezone.now():
        return  # Not finished yet

    total = Pledge.objects.filter(
        campaign=campaign,
        status__in=['authorized', 'captured']
    ).aggregate(total=Sum('amount'))['total'] or 0

    if campaign.model == 'aon' and total < campaign.goal_amount:
        campaign.status = 'failed'
        campaign.save()
        cancel_pledges_for_campaign.delay(campaign_id)
    else:
        campaign.status = 'funded'
        campaign.save()
        capture_pledges_for_campaign.delay(campaign_id)
        notify_creator_campaign_success.delay(campaign_id)

Payouts to Creators

Stripe Connect is standard for marketplaces. Creators connect bank accounts via onboarding:

def create_connect_account(creator):
    account = stripe.Account.create(
        type='express',
        country='RU',
        email=creator.email,
        capabilities={
            'card_payments': {'requested': True},
            'transfers': {'requested': True},
        },
    )
    creator.stripe_account_id = account.id
    creator.save()

    link = stripe.AccountLink.create(
        account=account.id,
        refresh_url='https://site.com/dashboard/connect/refresh',
        return_url='https://site.com/dashboard/connect/complete',
        type='account_onboarding',
    )
    return link.url


def transfer_to_creator(campaign, net_amount):
    """net_amount = collected - platform commission"""
    transfer = stripe.Transfer.create(
        amount=int(net_amount * 100),
        currency=campaign.currency.lower(),
        destination=campaign.creator.stripe_account_id,
        metadata={'campaign_id': str(campaign.id)},
    )
    return transfer

Frontend: Progress Bar and Real-time Updates

// Progress via Server-Sent Events
// GET /api/campaigns/:id/progress

export async function* campaignProgressStream(campaignId: string) {
  while (true) {
    const stats = await getCampaignStats(campaignId)
    yield `data: ${JSON.stringify(stats)}\n\n`
    await sleep(10_000) // update every 10 seconds
  }
}

function useCampaignProgress(campaignId: string) {
  const [progress, setProgress] = useState<CampaignStats | null>(null)

  useEffect(() => {
    const source = new EventSource(`/api/campaigns/${campaignId}/progress`)
    source.onmessage = (e) => setProgress(JSON.parse(e.data))
    return () => source.close()
  }, [campaignId])

  return progress
}

Commission Model

Standard: platform takes 5–8% from collected amount plus Stripe transaction fee (1.4% + 25₽ for European cards, 2.9% + 30¢ for others). Platform commission deducted via application_fee_amount when creating PaymentIntent:

intent = stripe.PaymentIntent.create(
    amount=10_000,  // 100 RUB
    currency='rub',
    application_fee_amount=600,  // 6% platform commission
    transfer_data={'destination': creator.stripe_account_id},
)

Timeframes

MVP crowdfunding platform (KIA model, Stripe, campaigns, rewards, basic dashboard): 6–8 weeks. Full AON platform with Connect, stretch goals, email notifications and analytics: 3–4 months.