• Stars
    star
    164
  • Rank 230,032 (Top 5 %)
  • Language
    JavaScript
  • License
    The Unlicense
  • Created over 11 years ago
  • Updated over 11 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Make your own Crowdfunding Site: Tutorial & Source Code

So You Wanna Build a Crowdfunding Site

Splash Image

The tools to get funded by the crowd, should belong to the crowd.

That's why I want to show you how to roll your own crowdfunding site, in less than 300 lines of code. Everything in this tutorial is open source, and we'll only use other open-source technologies, such as Node.js, MongoDB, and Balanced Payments.

Here's the Live Demo. All source code and tutorial text is Unlicensed.

  1. Quick Start

If you just want the final crowdfunding site, clone this repo and go to the /demo folder. All you need to do is set your configuration variables, and you're ready to go! For everyone who wants the nitty gritty details, carry on.

  1. Setting up a basic Node.js app with Express

If you haven't already done so, you'll need to install Node.js. (duh)

Create a new folder for your app. We'll be using the Express.js framework to make things a lot more pleasant. To install the Express node module, run this on the command line inside your app's folder:

npm install express

Next, create a file called app.js, which will be your main server logic. The following code will initialize a simple Express app, which just serves a basic homepage and funding page for your crowdfunding site.

// Configuration
var CAMPAIGN_GOAL = 1000; // Your fundraising goal, in dollars

// Initialize an Express app
var express = require('express');
var app = express();
app.use("/static", express.static(__dirname + '/static')); // Serve static files
app.use(express.bodyParser()); // Can parse POST requests
app.listen(1337); // The best port
console.log("App running on http://localhost:1337");

// Serve homepage
app.get("/",function(request,response){

	// TODO: Actually get fundraising total
    response.send(
    	"<link rel='stylesheet' type='text/css' href='/static/fancy.css'>"+
        "<h1>Your Crowdfunding Campaign</h1>"+
        "<h2>raised ??? out of $"+CAMPAIGN_GOAL.toFixed(2)+"</h2>"+
        "<a href='/fund'>Fund This</a>"
    );

});

// Serve funding page
app.get("/fund",function(request,response){
	response.sendfile("fund.html");
});

Create another file named fund.html. This will be your funding page.

<link rel='stylesheet' type='text/css' href='/static/fancy.css'>
<h1>Donation Page:</h1>

Optionally, you may also include a stylesheet at /static/fancy.css, so that your site doesn't look Hella Nasty for the rest of this tutorial.

@import url(https://fonts.googleapis.com/css?family=Raleway:200);
body {
	margin: 100px;
	font-family: Raleway; /* Sexy font */
	font-weight: 200;
}

Finally, run node app on the command line to start your server!

Check out your crowdfunding site so far at http://localhost:1337.

Crowdfunding Homepage 1

The homepage will display the Campaign Goal you set in the Configuration section of app.js. The donations page isn't functional yet, so in the following chapters, I'll show you how to accept and aggregate credit card payments from your wonderful backers.

  1. Getting started with Balanced Payments

Balanced Payments isn't just another payments processor. They've open sourced their whole site, their chat logs are publicly available, and they even discuss their roadmap in the open. These people get openness.

Best of all, you don't even need to sign up to get started with Balanced!

Just go to this link, and they'll generate a brand-new Test Marketplace for you, that you can claim with an account afterwards. Remember to keep this tab open, or save the URL, so you can come back to your Test Marketplace later.

Balanced Test Marketplace

Click the Settings tab in the sidebar, and note your Marketplace URI and API Key Secret.

Balanced Settings

Copy these variables to the Configuration section of app.js like this:

// Configuration
var BALANCED_MARKETPLACE_URI = "/v1/marketplaces/TEST-YourMarketplaceURI";
var BALANCED_API_KEY = "YourAPIKey";
var CAMPAIGN_GOAL = 1000; // Your fundraising goal, in dollars

Now, let's switch back to fund.html to create our actual payment page.

First, we'll include and initialize Balanced.js. This Javascript library will securely tokenize the user's credit card info, so your server never has to handle the info directly. Meaning, you will be free from PCI regulations. Append the following code to fund.html, replacing BALANCED_MARKETPLACE_URI with your actual Marketplace URI:

<!-- Remember to replace BALANCED_MARKETPLACE_URI with your actual Marketplace URI! -->
<script src="https://js.balancedpayments.com/v1/balanced.js"></script>
<script>
	var BALANCED_MARKETPLACE_URI = "/v1/marketplaces/TEST-YourMarketplaceURI";
	balanced.init(BALANCED_MARKETPLACE_URI);
</script>

Next, create the form itself, asking for the user's Name, the Amount they want to donate, and other credit card info. We will also add a hidden input, for the credit card token that Balanced.js will give us. The form below comes with default values for a test Visa credit card. Append this to fund.html:

<form id="payment_form" action="/pay/balanced" method="POST">

	Name: <input name="name" value="Pinkie Pie"/> <br>
	Amount: <input name="amount" value="12.34"/> <br>
	Card Number: <input name="card_number" value="4111 1111 1111 1111"/> <br>
	Expiration Month: <input name="expiration_month" value="4"/> <br>
	Expiration Year: <input name="expiration_year" value="2050"/> <br>
	Security Code: <input name="security_code" value="123"/> <br>

	<!-- Hidden inputs -->
	<input type="hidden" name="card_uri"/>

</form>
<button onclick="charge();">
	Pay with Credit Card
</button>

Notice the Pay button does not submit the form directly, but calls a charge() function instead, which we are going to implement next. The charge() function will get the credit card token from Balanced.js, add it as a hidden input, and submit the form. Append this to fund.html:

<script>

// Get card data from form.
function getCardData(){
	// Actual form data
	var form = document.getElementById("payment_form");
	return {
		"name": form.name.value,
		"card_number": form.card_number.value,
		"expiration_month": form.expiration_month.value,
		"expiration_year": form.expiration_year.value,
		"security_code": form.security_code.value
	};
}

// Charge credit card
function charge(){

	// Securely tokenize card data using Balanced
	var cardData = getCardData();
	balanced.card.create(cardData, function(response) {
		
		// Handle Errors (Anything that's not Success Code 201)
		if(response.status!=201){
			alert(response.error.description);
			return;
		}

		// Submit form with Card URI
		var form = document.getElementById("payment_form");
		form.card_uri.value = response.data.uri;
		form.submit();
		
	});

};

</script>

This form will send a POST request to /pay/balanced, which we will handle in app.js. For now, we just want to display the card token URI. Paste the following code at the end of app.js:

// Pay via Balanced
app.post("/pay/balanced",function(request,response){

	// Payment Data
	var card_uri = request.body.card_uri;
	var amount = request.body.amount;
	var name = request.body.name;

	// Placeholder
	response.send("Your card URI is: "+request.body.card_uri);

});

Restart your app, (Ctrl-C to exit, then node app to start again) and go back to http://localhost:1337.

Your payment form should now look like this:

Funding Form 1

The default values for the form will already work, so just go ahead and click Pay With Credit Card. (Make sure you've replaced BALANCED_MARKETPLACE_URI in fund.html with your actual Test Marketplace's URI!) Your server will happily respond with the generated Card URI Token.

Funding Form 2

Next up, we will use this token to actually charge the given credit card!

  1. Charging cards through Balanced Payments

Before we charge right into this, (haha) let's install two more Node.js modules for convenience.

Run the following in the command line:

npm install request A library for simplified HTTP requests.

npm install q A Promises library, to pleasantly handle asynchronous calls and avoid Callback Hell.

Because we'll be making multiple calls to Balanced, let's also create a helper method. The following function returns a Promise that the Balanced API has responded to whatever HTTP Request we just sent it. Append this code to app.js:

// Calling the Balanced REST API
var Q = require('q');
var httpRequest = require('request');
function _callBalanced(url,params){

	// Promise an HTTP POST Request
	var deferred = Q.defer();
	httpRequest.post({
		
		url: "https://api.balancedpayments.com"+BALANCED_MARKETPLACE_URI+url,
		auth: {
			user: BALANCED_API_KEY,
			pass: "",
			sendImmediately: true
		},
		json: params

	}, function(error,response,body){

		// Handle all Bad Requests (Error 4XX) or Internal Server Errors (Error 5XX)
		if(body.status_code>=400){
			deferred.reject(body.description);
			return;
		}
		
		// Successful Requests
		deferred.resolve(body);

	});
	return deferred.promise;

}

Now, instead of just showing us the Card Token URI when we submit the donation form, we want to:

  1. Create an account with the Card URI
  2. Charge said account for the given amount (note: you'll have to convert to cents for the Balanced API)
  3. Record the transaction in the database (note: we're skipping this for now, and covering it in the next chapter)
  4. Render a personalized message from the transaction

Replace the app.post("/pay/balanced", ... ); callback from the previous chapter with this:

// Pay via Balanced
app.post("/pay/balanced",function(request,response){

	// Payment Data
	var card_uri = request.body.card_uri;
	var amount = request.body.amount;
	var name = request.body.name;

	// TODO: Charge card using Balanced API
	/*response.send("Your card URI is: "+request.body.card_uri);*/

	Q.fcall(function(){

		// Create an account with the Card URI
		return _callBalanced("/accounts",{
			card_uri: card_uri
		});

	}).then(function(account){

		// Charge said account for the given amount
		return _callBalanced("/debits",{
			account_uri: account.uri,
			amount: Math.round(amount*100) // Convert from dollars to cents, as integer
		});

	}).then(function(transaction){

		// Donation data
		var donation = {
			name: name,
			amount: transaction.amount/100, // Convert back from cents to dollars.
			transaction: transaction
		};

		// TODO: Actually record the transaction in the database
		return Q.fcall(function(){
			return donation;
		});

	}).then(function(donation){

		// Personalized Thank You Page
		response.send(
    		"<link rel='stylesheet' type='text/css' href='/static/fancy.css'>"+
			"<h1>Thank you, "+donation.name+"!</h1> <br>"+
			"<h2>You donated $"+donation.amount.toFixed(2)+".</h2> <br>"+
			"<a href='/'>Return to Campaign Page</a> <br>"+
			"<br>"+
			"Here's your full Donation Info: <br>"+
			"<pre>"+JSON.stringify(donation,null,4)+"</pre>"
		);

	},function(err){
		response.send("Error: "+err);
	});

});

Now restart your app, and pay through the Donation Page once again. (Note: To cover processing fees, you have to pay more than $0.50 USD) This time, you'll get a full Payment Complete page, with personalized information!

Transaction 1

Furthermore, if you check the transactions tab in your Test Marketplace dashboard, you should find that money has now been added to your balance.

Transaction 2

We're getting close! Next, let's record donations in a MongoDB database.

  1. Recording donations with MongoDB

MongoDB is a popular open-source NoSQL database. NoSQL is especially handy for rapid prototyping, because of its dynamic schemas. In other words, you can just make stuff up on the fly. This will be useful if, in the future, you want to record extra details about each donation, such as the donator's email address, reward levels, favorite color, etc.

Start up a MongoDB database, and get its URI. You can use a remote database with a service such as MongoHQ, but for this tutorial, let's run MongoDB locally. Here are the instructions for installing and running MongoDB on your computer.

Once you've done that, add the MongoDB URI to your Configuration section at the top of app.js.

// Configuration
var MONGO_URI = "mongodb://localhost:27017/test";
var BALANCED_MARKETPLACE_URI = "/v1/marketplaces/TEST-YourMarketplaceURI";
var BALANCED_API_KEY = "YourAPIKey";
var CAMPAIGN_GOAL = 1000; // Your fundraising goal, in dollars

Now, let's install the native MongoDB driver for Node.js:

npm install mongodb

Add the following code to the end of app.js. This will return a Promise that we've recorded a donation in MongoDB.

// Recording a Donation
var mongo = require('mongodb').MongoClient;
function _recordDonation(donation){

	// Promise saving to database
	var deferred = Q.defer();
	mongo.connect(MONGO_URI,function(err,db){
		if(err){ return deferred.reject(err); }

		// Insert donation
		db.collection('donations').insert(donation,function(err){
            if(err){ return deferred.reject(err); }

            // Promise the donation you just saved
			deferred.resolve(donation);

			// Close database
			db.close();

		});
	});
	return deferred.promise;

}

Previously, we skipped over actually recording a donation to a database. Go back, and replace that section of code with this:

// TODO: Actually log the donation with MongoDB
/*return Q.fcall(function(){
	return donation;
});*/

// Record donation to database
return _recordDonation(donation);

Restart your app, and make another donation. If you run db.donations.find() on your MongoDB instance, you'll find the donation you just logged!

Transaction 3

Just one step left...

Finally, we will use these recorded donations to calculate how much money we've raised.

  1. Completing the Donation

Whether it's showing progress or showing off, you'll want to tell potential backers how much your campaign's already raised.

To get the total amount donated, simply query for all donation amounts from MongoDB, and add them up. Here's how you do that with MongoDB, with an asynchronous Promise for it. Append this code to app.js:

// Get total donation funds
function _getTotalFunds(){

	// Promise the result from database
	var deferred = Q.defer();
	mongo.connect(MONGO_URI,function(err,db){
		if(err){ return deferred.reject(err); }

		// Get amounts of all donations
		db.collection('donations')
		.find( {}, {amount:1} ) // Select all, only return "amount" field
		.toArray(function(err,donations){
			if(err){ return deferred.reject(err); }

			// Sum up total amount, and resolve promise.
			var total = donations.reduce(function(previousValue,currentValue){
				return previousValue + currentValue.amount;
			},0);
			deferred.resolve(total);

			// Close database
			db.close();

		});
	});
	return deferred.promise;

}

Now, let's go back to where we were serving a basic homepage. Let's change that, to actually calculate your total funds, and show the world how far along your campaign has gotten.

// Serve homepage
app.get("/",function(request,response){
    
    // TODO: Actually get fundraising total
    /*response.send(
        "<link rel='stylesheet' type='text/css' href='/static/fancy.css'>"+
        "<h1>Your Crowdfunding Campaign</h1>"+
        "<h2>raised ??? out of $"+CAMPAIGN_GOAL.toFixed(2)+"</h2>"+
        "<a href='/fund'>Fund This</a>"
    );*/

	Q.fcall(_getTotalFunds).then(function(total){
		response.send(
			"<link rel='stylesheet' type='text/css' href='/static/fancy.css'>"+
	        "<h1>Your Crowdfunding Campaign</h1>"+
	        "<h2>raised $"+total.toFixed(2)+" out of $"+CAMPAIGN_GOAL.toFixed(2)+"</h2>"+
	        "<a href='/fund'>Fund This</a>"
		);
	});

});

Restart the app, and look at your final homepage.

Crowdfunding Homepage 2

It's... beautiful.

You'll see that your total already includes the donations recorded from the previous chapter. Make another payment through the Donations Page, and watch your funding total go up.

Congratulations, you just made your very own crowdfunding site!

More Repositories

1

trust

An interactive guide to the game theory of cooperation
JavaScript
5,682
star
2

loopy

A tool for thinking in systems
JavaScript
1,617
star
3

polygons

A playable post on how harmless choices can make a harmful world.
JavaScript
1,328
star
4

wbwwb

We Become What We Behold – a minigame about the news!
JavaScript
1,140
star
5

sight-and-light

A programming tutorial for sight & light
HTML
1,049
star
6

nothing-to-hide

All the newest work on Nothing To Hide goes here. It's not clean, it's not organized, but it's *everything*.
JavaScript
773
star
7

joy

make happy little programs
JavaScript
561
star
8

nutshell

Make expandable explanations!
JavaScript
492
star
9

crowds

The Wisdom and/or Madness of the Crowds
HTML
432
star
10

anxiety

JavaScript
400
star
11

fireflies

Fireflies: an example of emergence
JavaScript
335
star
12

coming-out-simulator-2014

Coming Out Simulator 2014
JavaScript
304
star
13

remember

An interactive comic on Spaced Repetition
HTML
303
star
14

door

A Valentine's Day one-minute minigame
JavaScript
242
star
15

covid-19

COVID-19 Futures, Explained With Playable Simulations
HTML
233
star
16

ballot

An interactive guide to alternative voting systems
HTML
213
star
17

neurons

Neurotic Neurons, an interactive explanation.
JavaScript
199
star
18

simulating

An interactive guide to thinking in systems!
JavaScript
185
star
19

ncase.github.io

my personal site, fo' realz
HTML
137
star
20

sim

Relaunch of EMOJI SIMULATOR 😘
JavaScript
85
star
21

matrix

Learn with THE MATRIX
JavaScript
74
star
22

anxiety-demo

AHHHHH!
JavaScript
74
star
23

mental-health

Mental Health Tips (ft. Anxiety Wolf)
HTML
70
star
24

nutshell-beta

Nutshell Beta!
JavaScript
43
star
25

attractors

prototypin' shtuff
HTML
35
star
26

blog

This is my bloooggggggggg
JavaScript
32
star
27

contact-tracing

A comic to explain how contact tracing apps can beat COVID-19 *and* Big Brother
HTML
31
star
28

emoji-prototype

emoji-based artificial life, coz why not
JavaScript
24
star
29

SocialMinusSpying

Social sharing buttons that don't spy on your audience.
JavaScript
24
star
30

StanfordTalk

how to explain things ЯEAL good
HTML
21
star
31

experiment-stats

A short experiment on expectation failure & learning
HTML
20
star
32

nothide-prototype

The polished prototype for Nothing to Hide
JavaScript
17
star
33

anime

I AM BECOME ANIME
JavaScript
15
star
34

NG.js

NG.JS - An HTML5 port of the Newgrounds Flash API.
JavaScript
14
star
35

birds

Angry Physics! A demonstration of See, Model, Predict
JavaScript
14
star
36

sprite2gif

A web app to convert spritesheets to gifs, aw yiss!
JavaScript
12
star
37

improv-wip

Improv JS Work-in-Progress
JavaScript
12
star
38

bees

BEES
JavaScript
11
star
39

conversation

A conversation engine in the style of The Walking Dead games
JavaScript
10
star
40

Pony-Wings

An HTML5 clone of Tiny Wings, except with ponies.
JavaScript
9
star
41

thanks

WALL! OF! THANKS!
HTML
9
star
42

ncase.me

My tiny website at ncase.me
CSS
9
star
43

newgrounds-js

A Javascript implementation of the Newgrounds API
JavaScript
8
star
44

cos

Coming Out Simulator - Mobile web version
JavaScript
8
star
45

crowds-prototype

the wisdom and/or madness of crowds
JavaScript
8
star
46

trust-hu

Hungarian translation of The Evolution of Trust
JavaScript
7
star
47

learn-wip

Learn How To Learn: WIP
JavaScript
6
star
48

learning

HTML
5
star
49

commonly.cc

Commonly.cc, a marketplace to unlock assets for the public domain
JavaScript
5
star
50

selfies

a game about journalistic ethics
JavaScript
5
star
51

neuron-prototype

a test thingy about neurons, boop boop
JavaScript
5
star
52

OVC2016

Slides & links for my OpenVizConf 2016 talk!
HTML
5
star
53

gamer

a game about gamers
JavaScript
5
star
54

campfire

an interactive story maker, with a focus on modability
JavaScript
5
star
55

nothing-to-browse

The Nothing To Hide Frontpage
HTML
5
star
56

forest-wip

(WIP) An interactive essay on understanding the world's complex systems, I guess
JavaScript
5
star
57

easy-easel

Yet another Canvas drawing app, except it exports to EaselJS code
JavaScript
5
star
58

polygons-es

Spanish translation of Parable of the Polygons, by David T Marchand
JavaScript
5
star
59

crossbreed.js

Multiple Inheritance in Javascript
JavaScript
5
star
60

polygons-pentagons

Pentagon remix, by dncnmcdougall
JavaScript
4
star
61

ink

I AM INK - A 2.5D VR Animation
JavaScript
4
star
62

anypay.js

Anypay.js - Pay what you want, however you want.
JavaScript
4
star
63

vr-comic

Virtual Reality comic - horizontal scrolling test
4
star
64

polygons-de

German translation of Parable of the Polygons
JavaScript
4
star
65

polarization-wip

Simulation of political polarization, WIP
JavaScript
4
star
66

double-slit

A tiny Double Slit sim, for Mithuna The Friendo
JavaScript
4
star
67

fireflies-de

German translation for Fireflies
JavaScript
3
star
68

simulating-wip

WIP for my next interactive essay, yayyy
JavaScript
3
star
69

vr

MY VR EXPERIMENTS, WHOO
JavaScript
3
star
70

poll-stats-june-2018

really half-assed stats
JavaScript
3
star
71

2.5D-VR-Tests

2.5D VR Tests, obviously
JavaScript
3
star
72

cccheckout

Your one-stop shop to download & attribute CC works from across the internet.
JavaScript
3
star
73

gamedev-garage

The Gamedev Garage Sale's code, open-sourced!
JavaScript
3
star
74

wbwwb-de

German translation for We Become What We Behold (Wir Werden, Was Wir Sehen)
JavaScript
3
star
75

hacking-the-government

A tiny experiment to hack gov data, with Shidash's help
JavaScript
3
star
76

pug

Pavlov's Pug
JavaScript
3
star
77

update-9-aug-2019

2
star
78

trust-et

Estonian translation (by Krista Vaabel)
JavaScript
2
star
79

polygons-pt-br

Brazilian Translation of PotP
JavaScript
2
star
80

fireflies-pt-br

Portuguese (Brazilian) translation of Fireflies
JavaScript
2
star
81

network-prototype

network sim thingy prototype
JavaScript
2
star
82

sccv

We Become What We Behold (Italian Translation)
JavaScript
2
star
83

nothide-blog

Nothing To Hide devblog
CSS
2
star
84

the-race-to-vr

blah blah blah blah
JavaScript
2
star
85

trust-bg

Bulgarian translation of Evolution o' Trust
JavaScript
2
star
86

eyeo2016

My EyeO 2016 Talk
2
star
87

polygons-hu

Hungarian translation for Parable of the Polygons
JavaScript
2
star
88

wbwwb-ar

Arabic translation of We Become What We Behold, thx to @_alexanderClay
JavaScript
2
star
89

polygons-nl

Dutch translation of Parable of the Polygons
HTML
2
star
90

polygons-hi

Hindi translation of Parable of the Polygons (by @anandapurv)
JavaScript
2
star
91

hackysack

JavaScript
2
star
92

px

Embed user-generated pixel art into your sites and games
JavaScript
2
star
93

cuttingedge-craftyy

The Cutting-edge Craftyy
JavaScript
2
star
94

nothing-to-art

Nothing To Hide Art Bundle
JavaScript
2
star
95

topology

Topology shenanigans with Rin Ray
2
star
96

secret

shhh, this is a seeeeecret
JavaScript
2
star
97

prison-wip

[WIP] systems, stories, and statistics on Mass Incarceration
JavaScript
2
star
98

polygons-fr

French translation of Parable of the Polygons
HTML
2
star
99

subbed

The post-sub page for the Nicky Newsletter
HTML
2
star
100

ncase-credits

where supporter credits & newsletter signup go
HTML
2
star