你想打造一个众筹网站吗?

由大众资助的工具应该属于大众。

这就是我想要向你展示如何用不到 300 行代码打造你自己的众筹网站的原因。本教程中的所有内容都是开源的,我们只使用其他开源技术,如 Node.js、MongoDB 和 Balanced Payments。

这里是 实时演示
所有源代码和教程文本都是 无许可的

0. 快速入门

如果你只想获取最终的众筹网站,请克隆 crowdfunding-tuts 仓库 并进入 /demo 文件夹。

你只需要设置你的配置变量,就可以开始使用了!对于想要了解详细细节的人,请继续阅读。

1. 使用 Express 设置一个基本的 Node.js 应用

如果你还没有这样做,你需要 安装 Node.js。(当然)

为你的应用创建一个新文件夹。我们将使用 Express.js 框架 来让事情变得更加轻松。为了安装 Express 节点模块,在你的应用文件夹的命令行中运行以下命令

npm install express

接下来,创建一个名为 app.js 的文件,它将作为你的主要服务器逻辑。以下代码将初始化一个简单的 Express 应用,
它只为你的众筹网站提供一个基本的首页和筹款页面。

// 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(
        ""+
        "

Your Crowdfunding Campaign

"+ "

raised ??? out of $"+CAMPAIGN_GOAL.toFixed(2)+"

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

创建另一个名为 fund.html 的文件。它将成为你的筹款页面。


Donation Page:

可选地,你也可以在 /static/fancy.css 中包含一个样式表,
以便你的网站在接下来的教程中看起来不那么糟糕。

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

最后,在命令行中运行 node app 来启动你的服务器!

http://localhost:1337 上查看你目前为止的众筹网站。

Crowdfunding Homepage 1

首页将显示你在 app.js 的配置部分设置的活动目标。捐赠页面目前还不功能,因此在接下来的章节中,我将向你展示如何从你的赞助者那里接受和汇总信用卡付款。

2. 开始使用 Balanced Payments

Balanced Payments 不仅仅是一个支付处理器。他们已经开源了他们的整个网站,他们的聊天记录是公开的,他们甚至在公开场合讨论他们的路线图。 这些人真正理解开放性。

最重要的是,你甚至不需要注册就可以开始使用 Balanced!

只需点击此链接,他们就会为你生成一个全新的测试市场,
你之后可以用一个帐户来认领它。请记住保持此选项卡处于打开状态,或者保存 URL,以便稍后可以回到你的测试市场。

Balanced Test Marketplace

点击侧边栏中的“设置”选项卡,并记下你的市场 URI 和 API 密钥。

Balanced Settings

将这些变量复制到 app.js 的配置部分,如下所示

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

现在,让我们切换回 fund.html 来创建我们的实际支付页面。

首先,我们将包含并初始化 Balanced.js。这个 JavaScript 库将安全地标记用户的信用卡信息,因此你的服务器永远不需要直接处理这些信息。这意味着,你将免受 PCI 监管。将以下代码追加到 fund.html 中,用你的实际市场 URI 替换 BALANCED_MARKETPLACE_URI




接下来,创建表单本身,要求用户的姓名、他们想要捐赠的金额和其他信用卡信息。我们还将添加一个隐藏的输入,用于 Balanced.js 将提供的信用卡标记。下面的表单包含测试 Visa 信用卡的默认值。将此追加到 fund.html

Name:
Amount:
Card Number:
Expiration Month:
Expiration Year:
Security Code:

请注意,付款按钮不会直接提交表单,而是调用一个 charge() 函数,我们将在下文中实现它。charge() 函数将从 Balanced.js 获取信用卡标记,
将其添加为一个隐藏的输入,并提交表单。将此追加到 fund.html


此表单将发送一个 POST 请求到 /pay/balanced,我们将在 app.js 中处理它。现在,我们只想显示卡标记 URI。将以下代码粘贴到 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);

});

重启你的应用(Ctrl-C 退出,然后 node app 重新启动),并返回 http://localhost:1337

你的支付表单现在应该看起来像这样

Funding Form 1

表单的默认值将可以直接使用,因此只需点击“使用信用卡付款”。(确保你已经用你的实际测试市场的 URI 替换了 fund.html 中的 BALANCED_MARKETPLACE_URI!)你的服务器将愉快地用生成的卡 URI 标记响应。

Funding Form 2

接下来,我们将使用此标记来实际扣款!

3. 通过 Balanced Payments 扣款

在我们开始扣款之前(哈哈),让我们安装另外两个 Node.js 模块,以便于使用。

在命令行中运行以下命令

# A library for simplified HTTP requests.
    npm install request
npm install q

一个 Promises 库,以便愉快地处理异步调用并避免回调地狱。

由于我们将向 Balanced 发出多个调用,因此让我们创建一个辅助方法。以下函数返回一个 Promise,表示 Balanced API 已经响应了我们刚刚发送的 HTTP 请求。将此代码追加到 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;

}

现在,当我们提交捐赠表单时,我们不再仅仅显示卡标记 URI,而是要

  1. 使用卡 URI 创建一个帐户
  2. 对上述帐户进行给定金额的扣款(注意:你需要将其转换为美分,以供 Balanced API 使用)
  3. 在数据库中记录交易(注意:我们现在跳过这一步,并在下一章中介绍)
  4. 渲染来自交易的个性化消息

用以下内容替换上一章中的 app.post("/pay/balanced", ... ); 回调

// 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(
            ""+
            "

Thank you, "+donation.name+"!


"+ "

You donated $"+donation.amount.toFixed(2)+".


"+ "Return to Campaign Page
"+ "
"+ "Here's your full Donation Info:
"+ "<pre>"+JSON.stringify(donation,null,4)+"</pre>" ); },function(err){ response.send("Error: "+err); }); });

现在重启你的应用,并再次通过捐赠页面付款。(注意:为了支付手续费,你需要支付超过 0.50 美元的金额)这一次,你会得到一个完整的付款完成页面,其中包含个性化的信息!

Transaction 1

此外,如果你查看测试市场仪表板中的交易选项卡,你应该会发现你的余额中已经添加了资金。

Transaction 2

我们快到了!接下来,让我们在 MongoDB 数据库中记录捐赠。

4. 使用 MongoDB 记录捐赠

MongoDB 是一个流行的开源 NoSQL 数据库。NoSQL 特别适合快速原型开发,因为它具有动态模式。换句话说,你可以随意创建东西。

如果将来你想记录有关每个捐赠的额外详细信息,比如捐赠者的电子邮件地址、奖励等级、最喜欢的颜色等,这将非常有用。

启动一个 MongoDB 数据库,并获取其 URI。你可以使用像 MongoHQ 这样的服务来使用远程数据库,但对于本教程,让我们在本地运行 MongoDB (在你的计算机上安装和运行 MongoDB 的说明)。

完成此操作后,将 MongoDB URI 添加到 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

现在,让我们安装 Node.js 的原生 MongoDB 驱动程序

npm install mongodb

将以下代码添加到 app.js 的末尾。这将返回一个 Promise,表示我们已经在 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;

}

之前,我们跳过了实际记录到数据库的捐赠。
返回并用以下内容替换代码的那部分

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

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

重启你的应用,并进行另一次捐赠。如果你在你的 MongoDB 实例上运行 db.donations.find(),你将找到你刚刚记录的捐赠!

Transaction 3

只剩下最后一步了…

最后,我们将使用这些记录的捐赠来计算我们筹集了多少资金。

5. 完成捐赠

无论是为了展示进度还是为了炫耀,你都需要告诉潜在的赞助者你的活动已经筹集了多少资金。

要获取总捐赠金额,只需查询 MongoDB 中的所有捐赠金额,并将它们加起来。以下是如何使用 MongoDB 进行操作,以及它的异步 Promise。将此代码追加到 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;

}

现在,让我们回到我们提供基本首页的地方。让我们更改它,以实际计算你的总资金,并向世界展示你的活动进展到何种程度。

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

    // TODO: Actually get fundraising total
    /*response.send(
        ""+
        "

Your Crowdfunding Campaign

"+ "

raised ??? out of $"+CAMPAIGN_GOAL.toFixed(2)+"

"+ "Fund This" );*/ Q.fcall(_getTotalFunds).then(function(total){ response.send( ""+ "

Your Crowdfunding Campaign

"+ "

raised $"+total.toFixed(2)+" out of $"+CAMPAIGN_GOAL.toFixed(2)+"

"+ "Fund This" ); }); });

重启应用,并查看你的最终首页。

Crowdfunding Homepage 2

它…太美了。

你会看到你的总金额已经包含了上一章记录的捐赠。通过捐赠页面进行另一次付款,并观察你的筹款总额上升。

恭喜你,你刚刚创建了自己的众筹网站!

– – –

在 Hacker News 上讨论

关于 Nick Liow

Thiel Fellow & Mozilla WebFWD 校友。使用 Commonly.cc 为创意共享进行众筹。

更多由 Nick Liow 撰写的文章…

关于 Robert Nyman [荣誉编辑]

技术布道师 & Mozilla Hacks 编辑。发表关于 HTML5、JavaScript 和开放网络的演讲和博客。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直在从事 Web 前端开发工作 - 在瑞典和纽约市。他定期在 http://robertnyman.com 上发布博客,喜欢旅行和结识新朋友。

更多由 Robert Nyman [荣誉编辑] 撰写的文章…


14 条评论

  1. Alex

    哇!很棒的指南,我还需要仔细阅读,但这绝对值得点赞!我将在我的博客上分享它!

    2013 年 7 月 8 日 下午 04:28

    1. Robert Nyman [编辑]

      很高兴你喜欢它!

      2013 年 7 月 8 日 下午 06:13

  2. Sneha Agarwal

    嗨,很高兴阅读这篇文章,感谢您发布它。我喜欢使用 Mozilla,但如果我打开超过 10 个网址,它就会崩溃,这是为什么呢?我过去一直使用 Firefox 浏览器分析代码,但由于卡死,无法进行操作……请您能否让它比 Chrome 更快!

    提前感谢!

    2013 年 7 月 9 日 上午 00:20

    1. Robert Nyman [编辑]

      听起来您遇到了一个特定问题。请与Firefox 支持联系以解决此问题。

      2013 年 7 月 9 日 上午 01:22

  3. Nathan

    我觉得我爱上了那个 Q 库。从现在开始我要使用它而不是 async.js。

    2013 年 7 月 11 日 下午 13:53

    1. Robert Nyman [编辑]

      很高兴听到它给你带来了启发!

      2013 年 7 月 11 日 下午 14:55

  4. Billy

    非常酷!我得试试这个演示。感谢您的精彩文章!

    2013 年 7 月 12 日 上午 07:25

  5. Marlon

    非常感谢!

    2013 年 7 月 12 日 上午 09:39

  6. Ricardo

    很棒的教程。可惜平衡支付并不在全球范围内提供。

    戴上我的批评帽:1) 斑马条纹有点伤眼 2) 并不真正需要承诺 3) 由于这很简单,嵌入式数据库如 LevelDB 或 SQLite 可能更合适。

    2013 年 7 月 12 日 下午 14:43

  7. Ferrari

    我喜欢这个例子中的 Promise 部分,只需要一个很好的案例来帮助我理解它,非常感谢!

    2013 年 7 月 14 日 上午 10:53

  8. Falconerie

    非常好的阅读。感谢您分享这篇内容。

    2013 年 7 月 14 日 上午 11:04

  9. Umair Aslam

    我对 Javascript 世界还很陌生。整个教程都是基于 Linux 的,如果我在 Windows 上呢?

    2013 年 7 月 18 日 上午 03:13

  10. Brad Davis

    我认为应该提到,如果您要将信用卡信息发送到您自己的服务器,您需要使用 HTTPS!我知道这是一个例子,这主要用于学习处理信用卡的流程,但如果您计划在生产中使用它,您需要确保您的服务器是安全的。

    话虽如此,这是一个很棒的例子。我之前从未听说过平衡,现在我肯定会在我的项目中使用它们。谢谢!

    2013 年 7 月 21 日 下午 12:17

  11. Bharad

    作为一个学习练习,我使用模板、jQuery 对其进行了一些增强,还添加了 Bootstrap CSS。

    https://github.com/t20/CrowdFunding

    2013 年 8 月 5 日 上午 11:42

本文的评论已关闭。