-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.js
421 lines (375 loc) · 15.5 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')
var config = require('./webpack.config')
var bodyParser = require('body-parser')
var request = require("request")
var url = require("url");
// library for securely handeling hashed cookie objects
var cookieSession = require("cookie-session")
// WePay library
var WePay = require("wepay").WEPAY;
// HTTPS server
var https = require("https")
var http = require("http")
// file reader
var fs = require("fs")
// express library for defining routes
var express = require("express");
// load app configuration settings
var app_config = require('./config')
// create the express app and define what port this is open on
var app = new (express)();
var port = app_config.port;
// load webpack compiler
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
app.use(webpackHotMiddleware(compiler));
// support JSON and url encoded bodies
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
// setup cookie based sessions
app.use(cookieSession({
name:"session",
secret: app_config.cookie_secret,
secure: true
}));
// load ssl certificate
// for more information refer to this StackOverflow post:
// http://stackoverflow.com/questions/11744975/enabling-https-on-express-js
//
// For help with generating an SSL certificate and key:
// https://devcenter.heroku.com/articles/ssl-certificate-self
var privateKey = fs.readFileSync(app_config.ssl.privateKey, "utf8");
var certificate = fs.readFileSync(app_config.ssl.certificate, "utf8");
var credentials = {key: privateKey, cert: certificate};
// point the app to the static folder
app.use('/static', express.static('static'));
/**
* Send main application page
*
* This will also invalidate any previously held session on the site.
* So refreshing the page effectively kills all session information.
*/
app.get("/", function(req, res) {
// make sure the session is null each time we reload the page
// dont want things persisting when people come back
req.session = null
res.sendFile(__dirname + '/index.html')
})
/**
* Verify that everything in the app configuration is going to work
*
* It checks to make sure that:
* - middleware_uri is set to use HTTPS
* - the private key and certificates are defined
*/
function verifyConfig(conf) {
console.log("Checking middleware uri protocol: ", url.parse(conf.middleware_uri).protocol)
var middleware_protocol = url.parse(conf.middleware_uri).protocol;
if (middleware_protocol != "https" && middleware_protocol != "https:") {
throw(new Error("Middleware URI is not HTTPS."));
}
if (!privateKey) {
throw(new Error("Private key is not defined. Check to make sure that the file actually exists and contains information"));
}
if(!certificate) {
throw(new Error("Private key is not defined. Check to make sure that the file actually exists and contains information"));
}
return true;
}
/**
* Send a response back to the client.
* This function also takes care of sending back errors when the WePay request fails.
* It will include the WePay error_description as well.
*
* @param package - the data to send back to the user
* @param res - ExpressJS response object
*/
function sendResponse(package, res) {
res.setHeader('Content-Type', 'application/json');
if ("error_code" in package) {
var error_package = {
"error_code":500,
"error_description":"wepay call died. Check server logs for more details.",
"error_message":package.error_description,
"original_error":package}
console.log("Sending error!\t", error_package);
return res.status(500).send(JSON.stringify(error_package));
}
else {
console.log("Sending package back to client");
return res.send(JSON.stringify(package));
}
}
/**
* Request data from the given wepay_endpoint, using the specified access_token and package. This function will immediately send the response back to the client
*
* @param res - Express response object
* @param wepay_endpoint - the WePay API endpoint we want to hit
* @param access_token - the access token we want to use with the request. NOTE: for certain endpoints, this can be *null*
* @param package - the package of data we want to send along with request. This can be an empty object depending on the endpoint
*/
function getWePayData(res, wepay_endpoint, access_token, package) {
var wepay_settings = {}
if (access_token) {
console.log("Aquired access_token: ", access_token);
wepay_settings.access_token = access_token;
}
var wepay = new WePay(wepay_settings);
wepay.use_staging();
console.log("Making request to wepay: ", wepay_endpoint, package);
try {
wepay.call(wepay_endpoint, package, function(response) {
sendResponse(JSON.parse(response.toString()), res);
});
}
catch(error) {
//res.setHeader('Content-Type', 'application/json');
console.log("ERROR WITH WEPAY: ", error);
res.status(500).send(JSON.stringify(error));
}
}
/*
* Given a resource, and package of data, send a request to the middleware.
* Once the request is complete, it will call the callback function provided.
*
* Refer to the middleware specification for more details about what resources are available and what each resource expects in it's data package
*
* @params resource - the resource that we want to search on the partner's database. This should be user, account, or payer
* @params data - the package we use to query information about the provided resource
* @params callback - a callback function to execute after the middleware returns information. Typically this is `parseMiddlewareResponse`
*/
function getDataFromMiddleware(resource, data, callback) {
var uri = app_config.middleware_uri+"/"+resource;
console.log("Requesting data from middleware: ", uri, data);
return request.post(
{
url: uri,
json: data,
headers: {
"Authorization":app_config.middleware_secret
}
},
callback
);
}
/*
* Parse the response from the middleware and decide what to do with it.
* If the middleware sends an error, raise that error back to the client
* If a wepay_endpoint is provided, then use the information provided by the client and request information from the provided endpoint with the wepay_package
* If no wepay_endpoint is provided, then just send the results from the middleware back to the client
*
* @params req - Expresses Request object
* @params res - Express Response object
* @params error - A JSON structure with error information (empty if no error occured)
* @params response - A detailed response object
* @params body - A JSON structure with returned data
* @params wepay_endpoint - The wepay_endpoint to hit after receiving a response from the middleware
* @params wepay_package - The package to send to the wepay_endpoint
*/
function parseMiddlewareResponse(req, res, error, response, body, wepay_endpoint, wepay_package) {
if (body.error) {
// send error
body.error_code = 500;
body.error_description = body.error_message;
return sendResponse(body, res);
}
else {
if (body.access_token) {
console.log("Setting access token cookie:\t", body.access_token);
//req.session.access_token = body.access_token;
return getWePayData(res, wepay_endpoint, body.access_token, wepay_package);
}
return sendResponse(body, res);
}
}
/**
* Get a user's access token from the middleware and then make the associated v2/user lookup call on the WePay API
*
* In the request body we expect:
* @param email - (optional) the email associated with the user
* @param account_id - (optional) an account_id
*
* Given either of these fields, the middleware should be able to handle it and give us back an access token.
*/
app.post("/user", function(req, res) {
console.log('Incoming user request: ', req.body);
var package = {};
// get the email from the search
var email = req.body.email;
var account_id = req.body.account_id;
// get the necessary data from our middleware function and then make the corresponding request to WePay
getDataFromMiddleware(
"user",
{
"account_owner_email":email,
"account_id": account_id
},
function(error, response, body) {
return parseMiddlewareResponse(req, res, error, response, body, "/user", {});
}
);
})
/*
* Send a request to /v2/account/find and return the response
*
* In the request body, we expect:
* @param account_id - (optional) an account_id tied to the access token set in a cookie
*
* The account_id field is optional. If it is present, we fetch only that accounts information via v2/account
* If it is not present, we fetch all accounts owned by the user who owns the access token via the v2/account/find endpoint
*/
app.post('/account', function(req, res){
console.log("Received request for account info: ", req.body);
var package = {};
var wepay_endpoint = "";
// if we have an account_id, then just look up that particular account
if (req.body.account_id) {
console.log("Received account_id, looking only for account: ", req.body.account_id);
package['account_id'] = req.body.account_id;
wepay_endpoint = "/account";
return getDataFromMiddleware(
"user",
{"account_id":req.body.account_id},
function(error, response, body){
parseMiddlewareResponse(req, res, error, response, body, wepay_endpoint, package)
});
}
// otherwise lookup all accounts associated with the provided email
console.log("No account_id. Looking for all accounts belonging to: ", req.body.email);
wepay_endpoint = "/account/find";
return getDataFromMiddleware("user", {"account_owner_email": req.body.email}, function(error, response, body){
parseMiddlewareResponse(req, res, error, response, body, wepay_endpoint, package);
});
})
/**
* This endpoint has two seperate actions depending on the parameters passed
* If no checkout_id is given, then this will get the 50 most recent checkouts for the given account_id
* If a checkout_id is given, then this will fetch information for that checkout specifically.
* Passing the checkout_id is useful for updating a checkout's info after performing an action with the dashboard.
*/
app.post("/checkout", function(req, res) {
// prep the package and wepay_endpoint we want to hit
console.log("Received request for checkout");
var package = {};
var wepay_endpoint = "";
if (req.body.checkout_id) {
package = {"checkout_id":req.body.checkout_id};
wepay_endpoint = "/checkout";
}
else {
package = {"account_id": req.body.account_id};
if (req.body.start && req.body.start != '') {
package.start = req.body.start;
}
wepay_endpoint = "/checkout/find";
}
return getDataFromMiddleware("user", {"account_id":req.body.account_id}, function(error, response, body){
parseMiddlewareResponse(req, res, error, response, body, wepay_endpoint, package);
});
})
/**
* Resend the confirmation email to a user
*/
app.post("/user/resend_confirmation", function(req, res){
getDataWithPackage(req, res, "/user/resend_confirmation", {});
})
/**
* Get a list of the 50 most recent withdrawals for the given account_id
*/
app.post("/withdrawal", function(req, res){
console.log("Received request for withdrawals");
var package = {"account_id":req.body.account_id};
return getDataFromMiddleware(
"user",
{"account_id":req.body.account_id},
function(error, response, body) {
parseMiddlewareResponse(req, res, error, response, body, "/withdrawal/find", package);
});
})
/**
* Perform a refund for a given checkout_id.
*
* This endpoint expects the following fields in the body of the request:
* @param checkout_id - the checkout_id
* @param refund_reason - the reason that the checkout is being refunded
* @param amount - (optional) initates a partial refund for the specified amount
* @param app_fee - (optional) initiates a partial refund for the specified app_fee amount
* The amount field should be used to perform a partial refund.
* When doing a partial refund, you can also pass a app_fee that specifies how much of the app_fee you want to refund
*
* If no amount is passed, this will do a full refund
*/
app.post("/refund", function(req, res) {
console.log("Received request for refund");
var package = {"checkout_id":req.body.checkout_id, "refund_reason":req.body.refund_reason};
if (req.body.amount != null || req.body.app_fee != null) {
if (req.body.amount) {
package['amount'] = req.body.amount;
}
if (req.body.app_fee) {
package['app_fee'] = req.body.app_fee;
}
}
return getDataFromMiddleware(
"user",
{"account_id":req.body.account_id},
function(error, response, body){
return parseMiddlewareResponse(req, res, error, response, body, "/checkout/refund", package)
}
);
})
/**
* Get reserve information from the WePay API.
* Requires an access token and an account_id
*
*/
app.post("/reserve", function(req, res) {
console.log("Received request for reserve");
return getDataFromMiddleware(
"user",
{"account_id": req.body.account_id},
function(error, response, body) {
return parseMiddlewareResponse(req, res, error, response, body, "/account/get_reserve_details", {"account_id":req.body.account_id});
}
);
});
/**
* Given a payer's unique identifying information (such as their email), get a list of all of their checkouts from the middleware
*/
app.post("/payer", function(req, res) {
console.log("Received request for payer");
// get the email from the search
var email = req.body.email;
// get the necessary data from our middleware function and then make the corresponding request to WePay
return getDataFromMiddleware(
"payer",
{"payer_email":email, "num_elements":50},
function(error, response, body) {
return parseMiddlewareResponse(req, res, error, response, body, null, null);
}
);
})
/*
* Given a credit_card_id (tokenized card) get more information about the card from the v2/credit_card WePay API endpoint
*/
app.post("/credit_card", function(req, res){
console.log("Received request for credit_card");
var credit_card_id = parseInt(req.body.credit_card_id);
getWePayData(res, "/credit_card", null, {"credit_card_id":credit_card_id, "client_id":app_config.client_id, "client_secret":app_config.client_secret});
})
/**
* Start the application
*
* Before we start, make sure that the app configuration meets the requirements
*/
verifyConfig(app_config);
var httpsServer = https.createServer(credentials, app);
httpsServer.listen(port, function(error) {
if (error) {
console.error(error)
} else {
console.info("==> 🌎 Listening on port %s. Open up https://localhost:%s/ in your browser.", port, port)
}
});