Developing a Mobile App for Crowdfunding
Crowdfunding—Kickstarter or Indiegogo on your smartphone. But if you oversimplify to "users pay for a project," you get an unworkable product. Real complexity: conditional fund holds (escrow), All-or-Nothing vs. Keep-it-All logic, reward tier system with different levels, and regulatory requirements for public fundraising.
Financing Models and Implementation
All-or-Nothing (AoN)—funds only deducted if goal reached. If campaign doesn't collect needed sum—all payments refunded. Implementation: when backing, place authorization hold on card (funds frozen but not deducted). On campaign success—capture, on failure—void.
# Stripe: create PaymentIntent with capture_method=manual
import stripe
def back_project(user_id: str, project_id: str, amount: int, reward_tier_id: str) -> dict:
payment_intent = stripe.PaymentIntent.create(
amount=amount,
currency="usd",
capture_method="manual", # don't deduct immediately
metadata={
"user_id": user_id,
"project_id": project_id,
"reward_tier_id": reward_tier_id
},
confirm=False # confirm on client
)
return {"client_secret": payment_intent.client_secret}
def capture_all_backers(project_id: str):
"""Called on successful campaign completion"""
backers = db.get_backers(project_id, status="authorized")
for backer in backers:
stripe.PaymentIntent.capture(backer.payment_intent_id)
db.update_backer_status(backer.id, "captured")
def refund_all_backers(project_id: str):
"""Called on campaign failure"""
backers = db.get_backers(project_id, status="authorized")
for backer in backers:
stripe.PaymentIntent.cancel(backer.payment_intent_id)
db.update_backer_status(backer.id, "cancelled")
Problem: card authorization has limited duration—for Stripe 7 days for most cards, max 31 days. Campaigns longer than 30 days—can't hold authorization entire time. Solution: deduct immediately (Keep-it-All model) or re-authorize shortly before expiry.
Keep-it-All (KiA)—funds deducted immediately when backing, regardless of goal. Technically simpler but less attractive to backers. Popular for charitable and creative projects.
Reward Tiers
Each project offers multiple tiers: "Support" for $10, "Early Access" for $50, "Exclusive Bundle" for $200. Each tier has available slots and current fill count.
// iOS: reward tier selection screen
struct RewardTierView: View {
let tier: RewardTier
let onSelect: () -> Void
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("$\(tier.amount)")
.font(.title2.bold())
Spacer()
if let limit = tier.backerLimit {
Text("\(tier.backerCount)/\(limit) backed")
.font(.caption)
.foregroundColor(tier.isSoldOut ? .red : .secondary)
}
}
Text(tier.title).font(.headline)
Text(tier.description).font(.body).foregroundColor(.secondary)
if let estimatedDelivery = tier.estimatedDelivery {
Label("Delivery: \(estimatedDelivery)", systemImage: "shippingbox")
.font(.caption)
}
Button(tier.isSoldOut ? "Sold Out" : "Select Tier") {
if !tier.isSoldOut { onSelect() }
}
.disabled(tier.isSoldOut)
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
Limited slots require pessimistic locking on server: when backing, lock reward_tier row for check and decrement counter in single transaction.
Campaign Progress and Social Mechanics
Project page is central content. Raised funds progress bar, countdown to campaign end, author updates stream, backer comments.
Technically most loaded moment: campaign's last day. All backers check progress simultaneously. WebSocket for real-time or aggressive polling (every 10 seconds in last hours)—with CDN caching.
// Android: WebSocket for live progress update
class CampaignProgressViewModel(
private val campaignId: String,
private val wsClient: WebSocketClient
) : ViewModel() {
val progress = MutableStateFlow<CampaignProgress?>(null)
init {
wsClient.connect("wss://api.yourplatform.com/campaigns/$campaignId/live")
viewModelScope.launch {
wsClient.messages.collect { message ->
val update = json.decodeFromString<CampaignProgressUpdate>(message)
progress.value = update.progress
}
}
}
override fun onCleared() {
wsClient.disconnect()
}
}
Project Updates and Backer Communication
Author publishes work progress updates. Backers receive push for each new update. Segmentation: notify only those who backed this specific project.
FCM topic-based messaging unsuitable—need targeted send via server recipient list. For each project store list of backer device_tokens.
Moderation and Anti-Fraud
Public fundraising attracts fraudulent projects. Basic protection:
- Manual moderation of new projects before publishing
- Author identity verification via KYC (passport + selfie)—mandatory for fund withdrawal
- Auto-limit on withdrawal until KYC confirmation
Integrate with KYC providers: Sumsub, Veriff, Onfido. Mobile app initiates KYC via provider SDK—process fully in native UI.
// iOS: Sumsub KYC flow
import IdensicMobileSDK
let sdk = SNSMobileSDK.build(
accessToken: kycAccessToken
)
.withHandlers(
onStatusDidChange: { [weak self] sdk, prevStatus in
if sdk.mainState == .approved {
self?.onKYCApproved()
}
},
onEvent: { sdk, event in }
)
.assemble()
sdk.present(from: self)
Fund Withdrawal for Authors
After successful campaign completion, author wants funds. Withdrawal scheme:
- KYC passed → can request withdrawal
- Platform retains commission (usually 5–10%)
- Remainder withdrawn via Stripe Connect (payout to bank account) or international transfer
Stripe Connect standard for marketplaces with payouts. Each author separate Connected Account.
Timeline Estimates
| Scope | Timeline |
|---|---|
| Basic: view and support projects, payment, history | 6–8 weeks |
| Author account: project creation, updates, analytics | +3–4 weeks |
| All-or-Nothing with escrow and auto-capture/refund | +2 weeks |
| KYC and withdrawal via Stripe Connect | +2–3 weeks |
Pricing is calculated individually after requirements analysis.







