크라우드펀딩 사이트를 만들어보고 싶나요?

크라우드 펀딩을 위한 도구는 ‘크라우드’ 것이여야 합니다.

이것이 제가 지금 300줄도 안되는 코드로 여러분만의 크라우드 펀딩 사이트를 구축할 수 있는 법을 알려드리는 이유입니다. 이 튜토리얼에 있는 모든 것들은 오픈 소스이며 Node.js같은 오픈 소스 기술만을 사용할 것입니다.

실시간 데모영상

모든 소스 코드와 튜토리얼 문서는 퍼블릭 도메인입니다.

0. 빠르게 시작하기

빠르게 크라우드펀딩 사이트를 만들고 싶다면 crowdfunding-tuts repository를 복사하여 /demo 폴더로 들어가세요.

여러분이 할 일은 오직 설정만 바꿔주면 됩니다. 좀 더 자세한 핵심 사항들을 더 알고싶은 분들은 계속 튜토리얼을 따라 읽으세요.

1. Express를 포함한 기본 Node.js 어플리케이션 설정하기

아직 Node.js가 준비가 되지 않은 분들은 Node.js를 설치하세요.

어플리케이션을 위한 새로운 폴더를 만듭니다. 좀 더 간편히 만들기 위해 Express.js 프레임워크를 사용할 것입니다. Express node 모듈을 설치하기 위해서는 터미널 상에서 새로 만든 폴더로 이동한 뒤 아래 명령어를 입력합니다:

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(
        "<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");
});

fund.html을 새로 만듭니다. 이 파일은 펀딩 페이지로 쓰입니다.

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

선택적으로 at /static/fancy.css의 stylesheet 파일을 첨부하여 매우 허전한 튜토리얼과는 다르게 이쁘게 꾸밀 수 있을 것입니다.

@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. 파일 Configuration 부분에 ‘Campaign Goal’ 란에 설정해놓은 문구가 뜰 것입니다. 기부 페이지는 아직 만들어지지 않은 상태입니다. 다음 챕터에서는 여러분의 후원자들을 위해 신용 카드 지불 시스템을 구축하는 방법을 알려드립니다.

2. ‘Balanced Payments’ 시작하기

Balanced Payments는 기존의 결제 모델과 다르지 않습니다. 사이트 전체가 오픈 소스로 공개되어 있고 챗 기록 또한 누구나 접근이 가능하며 심지어 로드맵에 대한 논의도 열려있습니다. 이 사람들의 개방에 대한 이야기를 한 번 읽어보세요.

무엇보다 중요한건 ‘Balanced’를 이용하기 위해 회원가입조차 할 필요가 없다는 사실입니다.

이 링크로 들어가보면, 여러분에게 새로운 테스트 마켓플레이스를 제공하는데 이 곳에서 이 후 여러분의 계좌에 대해 청구할 수 있습니다. 이 사이트의 탭을 계속 열어놓거나 URL을 복사해서 언제든지 다시 테스트 마켓플레이스에 접속할 수 있도록 해두세요.

Balanced Test Marketplace

좌측 사이드바에 있는 Settings 메뉴에 들어가 여러분의 마켓플레이스 URI와 비밀 API Key를 적어놓으세요.

Balanced Settings

아래 처럼 여러분의 app.js 파일 Configuration 부분에 복사해 넣습니다:

// 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 파일을 첨부합니다. 이 자바스크립트 라이브러리는 사용자의 신용카드 정보를 안전하게 토큰화 시켜주는 역할을 해줘서 서버가 사용자의 정보를 직접 다룰 필요가 없도록 도와줍니다. PCI(Payment Card Industry) 규제를 신경쓰지 않아도 되는 것을 의미합니다. BALANCED_MARKETPLACE_URI 자리에 여러분의 진짜 Marketplace URI로 바꾸어 아래의 소스 코드를 fund.html에 추가합니다:

<!-- 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>

다음으로 사용자의 이름, 기부액, 나머지 신용카드 정보를 입력할 수 있는 폼을 만듭니다. 추가적으로 숨겨진 input 폼에 Balanced.js에서 제공하는 신용카드 토큰을 추가할 것입니다. 아래의 폼은 Visa 용 신용카드에 맞는 기본적인 값을 포함한 예제입니다. 이것을 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>

결제 버튼을 클릭할 때 직접 폼을 전송하지 않고 charge()함수를 대신 불러오는 것을 명심하세요. 이 함수는 다음에 구현할 것입니다. charge() 함수는 Balanced.js 로부터 신용카드 토큰을 얻어와 숨겨진 input 폼에 넣어 폼을 전송하는 역할을 합니다. 아래의 소스 코드를 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>

이 폼은 app.js 에서 제어하는 /pay/balanced 로 POST 요청을 보냅니다. 지금은 단순히 카드 토큰 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

이 폼의 기본 값들은 이미 동작하는 값들이기 때문에 계속 진행하여 Pay With Credit Card 버튼을 눌러봅니다. (여러분의 실제 테스트 마켓플레이스 URI를 infund.html 파일의 BALANCED_MARKETPLACE_URI 자리에 바꿔 넣는 것을 잊지마세요!) 여러분의 서버가 생성된 URI 토큰에 응답하는 것을 확인할 수 있습니다.

Funding Form 2

 

다음에는 이 토큰을 사용하여 실제 신용카드에 청구하는 방법을 다뤄볼 것입니다.

3. Balanced Payments를 사용하여 청구하기

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(
            "<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);
    });

});

이제 어플레케이션을 재시작하여 다시 기부 페이지를 통해 결제를 해봅시다. (참고: 수수료 $0.50(USD)을 더 지불해야 합니다. ) 아까와는 다르게 이번에는 개인 메시지와 함께 최종 결제 완료 페이지를 볼 수 있을 것입니다.

Transaction 1

더욱이, 여러분의 테스트 마켓플레이스 화면의 ‘transaction’ 탭을 들어가보면, 총액에 방금 결제된 금액이 추가된 것을 확인하실 수 있습니다.Transaction 2

거의 끝났습니다! 다음으로 MongoDB 데이터베이스에 기부 거래를 기록으로 남겨봅시다.

4. MongoDB에 결제 기록 남기기

MongoDB 는 잘 알려진 오픈 소스로 공개된 NoSQL 데이터베이스입니다. NoSQL은 다이나믹 스키마 특징을 가지고 있어 특히 빠르게 프로토타입을 만들기 쉬운 도구입니다. 한 마디로, 즉시 무언가를 만들어 내기에 참 좋습니다.

나중에 NoSQL은 기부자의 이메일이나 보상 레벨, 좋아하는 색깔 등의 각 기부에 대한 추가적인 세부기록들을 남기기에 유용할 수 있습니다.

MongoDB 데이터베이스를 시작하고 URI를 복사합니다. MongoHQ 같은 서비스로 원격 데이터베이스를 사용할 수도 있지만, 이번 튜토리얼에서는 로컬에서 MongoDB를 조작해 볼 것입니다(MongoDB 설치 및 실행하기).

일단 MongoDB 설치와 실행이 끝나면, app.js.의 가장 위에 위치한 Configuration 부분에 MongoDB URI를 붙여 넣습니다.

// 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 끝에 다음 소스코드를 붙여 넣습니다. 이 부분은 MongoDB에 기부 기록을 남겼던 Promise를 반환합니다.

// 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);

어플리케이션을 재시작하고 다시 기부를 해봅시다. db.donations.find()를 여러분의 MongoDB 인스턴스에서 실행해보면, 다음처럼 여러분이 남겼던 기부 기록들을 확인하실 수 있습니다!

Transaction 3

이제 마지막 한 단계만 남았습니다…

마지막으로 총 기부금이 얼마나 모였는지 남겨진 기부 기록을 가지고 계산해 보겠습니다.

5. 기부 완료하기

진행 중이거나 혹은 완료 이에도 여러분이 시작한 캠페인에 얼만큼의 기부금이 모였는지 잠재적인 후원자들에게 알려주고 싶을 것입니다.

총 기부금을 가져오기 위해, MongoDB에 모든 기부금들을 얻어오는 간단한 쿼리를 보내 그것들을 전부 합산합니다. 아래 소스코드의 내용은 비동기적 Promise를 가지고 MongoDB에서 작업하는 방법입니다. 이 소스코드를 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(
        "<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>"
        );
    });

});

앱을 재시작하면 최종으로 완성된 홈페이지를 볼 수 있습니다.

Crowdfunding Homepage 2

정말….멋지지 않나요?

여러분의 포탈은 이미 이전 챕터에서 기록된 기부금을 포함하고 있음을 볼 수 있습니다. 기부 페이지를 통해 다시 결제를 하면 펀딩 총액이 증가하는 것을 확인할 수 있습니다.

축하드립니다! 이제 막 여러분만의 크라우드 펀딩 사이트를 갖게 되셨습니다!

작성자: Hoony Chang

Web Programmer

Hoony Chang가 작성한 문서들…


댓글이 없습니다.

댓글 쓰기