DEV Community

Sneha Shri
Sneha Shri

Posted on

The Crowdfunding App That Almost Charged Nobody: Lessons in Building Pledge Systems That Actually Work

Maya had been planning her platform for months.

The idea was simple. Independent board game designers would launch campaigns, backers would pledge money for early copies, and if a campaign hit its goal within 30 days, everyone got charged and the game got made. If it missed the goal, nobody paid a cent.

She hired a small team, and they built the first version quickly. Campaign pages looked great. The pledge button worked. Test payments went through.

Then came the first real campaign.

It hit its goal on day 27. The team celebrated. On day 30, the system tried to collect the money.

Almost every charge failed.

This is the story of what went wrong, and what a crowdfunding platform really needs under the hood. If you're working on crowdfunding app development, these are the lessons worth learning before your first campaign goes live.

Lesson 1: A card authorization doesn't last 30 days

The team's original plan felt logical. When a backer pledged, the app placed an authorization hold on their card for the pledge amount. When the campaign succeeded, the app would capture those holds.

The problem is that card authorizations expire. With Stripe, for example, an uncaptured online card payment is typically only valid for around seven days. After that, the hold is released and the capture fails.

A 30-day campaign simply can't rely on holding funds from day one.

The fix: save the payment method, not the payment.

Instead of authorizing the amount at pledge time, the team switched to saving the backer's card for future use. With Stripe, that means a SetupIntent at pledge time, then an off-session PaymentIntent when the campaign succeeds.

javascript
// At pledge time: save the card, don't charge it
const setupIntent = await stripe.setupIntents.create({
customer: backer.stripeCustomerId,
usage: "off_session",
metadata: { campaignId, pledgeId },
});

// When the campaign succeeds: charge each saved card
const payment = await stripe.paymentIntents.create(
{
amount: pledge.amountInCents,
currency: "usd",
customer: backer.stripeCustomerId,
payment_method: pledge.paymentMethodId,
off_session: true,
confirm: true,
metadata: { campaignId, pledgeId },
},
{ idempotencyKey: pledge-charge-${pledgeId} }
);

Two details matter here. The idempotencyKey means that if the job retries, the backer is never charged twice. And some cards will still need extra authentication (like 3D Secure) at charge time, so the platform needs a flow that emails those backers a link to complete payment.

Lesson 2: Campaigns need a real state machine

In Maya's first version, a campaign's status was just a text field that different parts of the code updated whenever they felt like it. That led to strange bugs, like a campaign showing "funded" while charges were still failing in the background.

A crowdfunding campaign has a clear lifecycle, and the code should enforce it:

DRAFT → UNDER_REVIEW → LIVE → SUCCEEDED → COLLECTING → FUNDED
↘ FAILED (goal not met)
COLLECTING → PARTIALLY_COLLECTED (some charges failed)

Each transition should happen in one place, with rules:

LIVE → SUCCEEDED only when the deadline passes and the goal is met
SUCCEEDED → COLLECTING only when the charge job starts
Payouts to the creator only after FUNDED, never before

A simple transition table in code, plus a database constraint on allowed statuses, prevents an entire class of bugs.

Lesson 3: Reward tiers can oversell in seconds

Maya's second campaign offered 100 "Early Bird" copies at a discount. They sold out in four minutes. The database said 113 were claimed.

The code checked availability, then inserted the pledge. Under load, several requests checked at the same moment, saw stock remaining, and all went through.

The fix: let the database enforce the limit atomically.

sql
UPDATE reward_tiers
SET claimed = claimed + 1
WHERE id = $1
AND claimed < quantity_limit
RETURNING claimed;

If no row comes back, the tier is sold out, and the pledge is rejected before it's saved. One statement, no race condition.

Lesson 4: Webhooks are the source of truth

The team originally marked charges as "paid" as soon as the API call returned. But payments can succeed, fail, or need action later. Disputes and refunds arrive days or weeks afterwards.

The platform needed to treat payment provider webhooks as the final word:

Verify every webhook signature
Store each event ID and ignore duplicates
Update the pledge status only from confirmed events
Reconcile daily, comparing your records with the payment provider's

This is also what makes accurate creator payouts possible.

Lesson 5: Trust features aren't optional

Crowdfunding platforms move money between strangers. Before Maya's platform could grow, it needed:

Creator verification before a campaign goes live
Campaign review to catch obvious fraud or prohibited products
Clear refund rules shown to backers before they pledge
Backer updates so creators can post progress after funding
An audit trail of every status change, charge, refund, and payout

None of these are glamorous, but they're what keep backers coming back for a second campaign.

How the story ended

After the rebuild, Maya's platform relaunched with saved payment methods, a strict campaign state machine, atomic reward inventory, and webhook-driven payment status.

The next campaign hit its goal on day 22. On day 30, the collection job ran. A handful of cards needed authentication, and those backers received an email with a payment link. Within 48 hours, the campaign moved to FUNDED, and the creator received their payout.

Nobody was charged twice. Nobody was charged for a sold-out reward. And nobody had to explain to angry backers why their money disappeared.

Building vs. getting help

If you're building a crowdfunding platform yourself, start with the payment flow, not the campaign page. That's where most of the real complexity lives.

If your team is small or the deadline is tight, working with an experienced crowdfunding app development company can save months of trial and error, especially around payments, compliance, and scaling. When evaluating crowdfunding app development services, ask directly how they handle authorization expiry, idempotent charges, and failed payment recovery. The answers will tell you a lot. The best crowdfunding app development solutions are the ones that get these invisible details right.

What's the trickiest payment edge case you've run into? I'd love to hear about it in the comments.

Top comments (0)