Skip to content

Commit bc5bb43

Browse files
Updated READMEs and logging output.
All log messages are written in the same format to both the console and a file. Errors are written to a separate file.
1 parent 0d8acc7 commit bc5bb43

File tree

7 files changed

+81
-37
lines changed

7 files changed

+81
-37
lines changed

README.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,29 @@
22
Kvasir (Vas-eer) is a support dashboard meant for WePay partners to be able to provide their end-users with basic support.
33
It will perform:
44

5-
- account lookups
5+
- Account Lookups
66
- Get the status of a merchant's account
7-
87
- Get a merchant's withdrawal info including their bank info and when the next withdrawal will take place
9-
10-
- user lookups
11-
12-
- resending user confirmation
13-
14-
- refunding checkouts
15-
8+
- Merchant (User) Lookups
9+
- Resending merchant confirmation
10+
- Refunding Checkouts
1611
- Both full and partial refunds are possible
12+
- Payment Method Lookups
13+
- Get the information that a given *payment_method_id* represents
14+
15+
## Full Documentation
16+
This README only contains a brief outline of what Kvasir is and what it is capable of. To view the whole documentation, head to http://jalepeno112.github.io/Kvasir/index.html
1717

1818
## Functionality
19-
All of the functionality has been tested, but there may be some cases that I missed. The WePay API isn't always predicatable, so if you run into an error, be sure to report it as an issue so that I can investigate furhter. Giving me the original call information (without your client_secret or any access tokens) is extremely helpful so that I can try and reproduce the error.
19+
All of the functionality has been tested, but there may be some cases that we missed. The WePay API isn't always predictable, so if you run into an error, be sure to report it as an issue so that we can investigate further. Giving me the original call information (without your client_secret or any access tokens as those are sensitive pieces of information you shouldn't share with anyone) is extremely helpful so that I can try and reproduce the error.
2020

2121
To see how the framework behaves in action go to: https://nameless-hollows-55554.herokuapp.com/
2222

23-
The test cases are found in the "test" directory.
24-
25-
## More Info
26-
To see more info and the full documentation on Kvasir's specs and what's required to use Kvasir, checkout the full documentation: https://wedemoapp.gitlab.io/kvasir/index.html
23+
## Test Cases
24+
The test cases are found in the "test" directory. They test the backend functionality and Kvasir's ability to interact with your middleware.
2725

2826
## Why Kvasir?
29-
`Kvasir is a Norse god of wisdom <http://norse-mythology.org/kvasir/>`_. In fact he's considered the wisest of all of them. He was killed by dwarves and his blood became the Mead of Poetry which was used to inspire poets and scholars. Basically, he's the personification of alcohol. Given that we were drinking when we came up with the idea for Kvasir, it only made sense.
27+
Kvasir is a Norse god of wisdom <http://norse-mythology.org/kvasir/>. He was killed by dwarves and his blood became the Mead of Poetry which was used to inspire poets and scholars. Basically, he's the personification of alcohol. Given that we were drinking when we came up with the idea for Kvasir, it only made sense.
3028

3129
## Who is this mystical "we" all over the documentation?
3230
We are many, but we are one.

docs/architecture.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Kvasir's Architecture
44
=========================
5-
This page details Kvasir's architecture. It explains in detail why each architecture decision was made.
5+
This page details Kvasir's architecture. We do this so that you have a better idea for why certain decisions are made in the :ref:`back-end <kvasirbackend>` and :ref:`front-end <kvasirfrontend>`.
66

77
A Brief History
88
-------------------
@@ -15,14 +15,14 @@ On the security side, many of the WePay API calls that we need to make required
1515

1616
While exposing access tokens to the front-end was considered a deal breaker, being able to get tokens to the front-end in the first place was also a challenge. No two database configurations are the same, and we wanted this solution to be available to any company that currently integrates with WePay. Normally, data makes up the fundamental building block of a web-application, but here we were presented with the unusual problem of having absolutely no control over the underlying database. Not only that, but many databases would likely not be optimized for use with Kvasir, so we could make no assumptions about how certain pieces of data were connected or about the presence of data.
1717

18-
A pure SPA with no attached back-end fell of the table pretty quickly, but we wanted to try and hold on to many of the benefits that come with a SPA such as:
18+
A SPA with no attached back-end fell of the table pretty quickly, but we wanted to try and hold on to many of the benefits that come with a SPA such as:
1919
1) **Being back-end agnostic**. Someone could simply take our front-end, and redesign the lower pieces and it should work just as well (if not better)
2020
2) **Easy setup and deployment**. One of the nice parts of an SPA is that all of the logic and external calls are contained in the app running in a user's browser. It only requires a small static server to push the necessary files forward, but the burden of the work falls on the user's machine.
2121

2222

2323
Architecture
2424
-----------------
25-
With all of these considerations in mind, we decided to stick with a SPA but provide it a sturdy back-end server for managing external API calls and database connections.
25+
With all of these considerations in mind, we decided to stick with a SPA design but provide it a sturdy back-end server for managing external API calls and database connections.
2626

2727
.. figure:: images/KvasirInitialArchitecture_Prototype.*
2828
:align: center
@@ -43,7 +43,7 @@ In order to achieve this, we came up with this idea of a :ref:`"middleware compo
4343
This middleware can be provided in any language that the developer chooses, using any structure that they want. As long as it can receive and send back information in the way that Kvasir expects, the rest is up to the developer. The information that Kvasir requests may be spread out across three or four tables for one developer, but be completed contained in a single table for another. This middleware piece allows Kvasir to be apathetic towards a developer's underlying database. This provides us with the necessary flexibility to be able to provide this as a solution to all platforms currently using WePay, not just a handful.
4444

4545

46-
Integration Environment with Kvasir
46+
How to Host Kvasir
4747
----------------------------------------
4848
The entire architectural model assumes that Kvasir and your middleware are running on your company's internal network. Kvasir does not come with any sort of authentication and there are *no plans for it*. Similarly to database configurations, there are a plethora of authentication systems out there that we cannot provide mechanisms to interact with all of them.
4949

docs/frontend.rst

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ React and Redux apps are comprised of 3 parts:
2525
Holding onto the idea that our application is really taking a user on a walk through their internal database, we have a set of "objects" or "models", each of which have their own set of actions, reducers and components.
2626

2727
These objects are:
28-
- :ref:`User <user_object>`
28+
- :ref:`User <user_object>`
2929
* represents a given *merchant*.
3030
* A merchant can have multiple accounts.
3131

@@ -55,12 +55,14 @@ If you look at these objects, you might recognize that all of these *except for
5555

5656
The *components* are responsible for handling user actions and then dispatching the associated Redux actions. They are also responsible for subscribing to all of the necessary state information and formatting that data. While all actions are globally published, not every component relies on all of that info (and they shouldn't).
5757

58-
For example, when an account is clicked in the account component, the account component registers that the click happened, manipulates the table, and then dispatches the *searchedAccounts*, *fetchWithdrawalsIfNeeded* and *fetchCheckoutsIfNeeded* actions. Some of these actions will directly impact the action component causing it to re-render with new info, while others will impact other components forcing them to re-render with the new information, but the *User* objects is not impacted at all. Actions to accounts do not affect the User who owns them.
58+
For example, when an account is clicked in the account component, the account component registers that the click happened, manipulates the table, and then dispatches the *searchedAccounts*, *fetchWithdrawalsIfNeeded* and *fetchCheckoutsIfNeeded* actions. Some of these actions will directly impact the action component causing it to re-render with new info, while others will impact other components forcing them to re-render with new info as well. On the other hand, the *User* objects is not impacted at all. Actions to accounts do not affect the User who owns them so we do not see the user component re-render.
5959

6060
General Object Implementation
6161
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6262
All of the objects are different in the sense that they require different search requirements (user_id, account_id, etc.); however, they are all implemented in very similar ways.
6363

64+
Actions
65+
^^^^^^^^^^
6466
All of the objects require a handful of actions:
6567
1) Search
6668
- Notify all components the object is being *searched* for and what exactly we are searching for
@@ -82,7 +84,7 @@ But not all of these actions are directly accessible. For example, request and
8284
In general, these are the public functions that each object has for dispatching actions:
8385
1) .. function:: search(id)
8486

85-
Will cause the associated reducer to update its state with the information the user passed in order to search the object.
87+
Will cause the associated reducer to update its state with the information the user passed in order to search the object. This is necessary so that we can verify that the info coming back is actually the info we requested.
8688

8789
:param id: some unique id of the object that we just looked up. For example, for user's this is an email address; accounts use an account_id
8890

@@ -94,6 +96,8 @@ In general, these are the public functions that each object has for dispatching
9496

9597
:param id: some unique id of the object that we just looked up. For example, for user's this is an email address; accounts use an account_id
9698

99+
Reducers
100+
^^^^^^^^^^^^
97101
The reducers that take these actions are also very similar.
98102
Each reducer is actually composed of two smaller functions - a *searched* function and a *base* function.
99103
We do this because of the asynchronous nature of Redux actions mixed with the POST requests to our back-end. If someone searches a user, but then realizes they searched the wrong email and changes the search parameter, we need a way to handle that.
@@ -128,7 +132,7 @@ Going back to the earlier example, if someone were to search a user with one ema
128132
.. _user_object:
129133

130134
User Object
131-
~~~~~~~~~~~~
135+
------------
132136
The user objects represents a WePay merchant accessible through the :wepay:`user` endpoint.
133137
This is the primary building block for all other information that we gather.
134138

@@ -149,7 +153,7 @@ The state of the user is important because if the user is not in the *registered
149153
.. _account_object:
150154

151155
Account Object
152-
~~~~~~~~~~~~~~~~
156+
----------------
153157
As soon we have a user's access token, we can also get a list of all of their merchant accounts tied to the app_id that the access token is associated with via the :wepay:`account find` call.
154158

155159
A user could have multiple accounts, so each account is displayed as a row in a larger table. Clicking on a row of the table will cause the row to become highlighted, and will dispatch actions to fetch more information about that specific account. This information includes withdrawals, reserves, and checkouts.
@@ -171,7 +175,7 @@ The account table itself includes:
171175
.. _withdrawal_object:
172176

173177
Withdrawal Object
174-
~~~~~~~~~~~~~~~~~~~~~
178+
------------------
175179
The withdrawal object represents information gained from the :wepay:`withdrawal` endpoint.
176180
This includes information about where a merchant's money is being withdrawn too, when it's being withdrawn, and how much is being withdrawn.
177181

@@ -182,7 +186,7 @@ These tables will render the 50 most recent withdrawals/reserves for a merchant.
182186
.. _checkouts_object:
183187

184188
Checkouts Object
185-
~~~~~~~~~~~~~~~~~~~
189+
------------------
186190
The checkout object is one of the more intensive objects. Since it is the heart of many operations that a platform performs, there are also several actions tied to any given checkout.
187191

188192
The checkout component renders a table of information gathered from a :wepay:`checkout find` call which includes:
@@ -214,7 +218,7 @@ The checkout component is also currently responsible for rendering the informati
214218
.. _credit_card_object:
215219

216220
Credit Card Object
217-
~~~~~~~~~~~~~~~~~~~
221+
--------------------
218222
The credit_card object represents information gathered by a :wepay:`credit_card` call. One of the benefits of WePay is the ability to tokenize payment information and simply store a token instead of all the payer's info. Storing all payer info requires a higher level of PCI compliance than just the token.
219223

220224
However, a platform may want to lookup information associated with a tokenized card at any point in time. The *Payment Method ID* column in the checkout object contains the tokenized id. Clicking on one of them (they are all hyperlinks) will dispatch actions to fetch more information about the card and render it in a table.
@@ -233,7 +237,7 @@ This table includes:
233237
.. _payer_object:
234238

235239
Payer Object
236-
~~~~~~~~~~~~~~~~
240+
-------------------
237241
As mentioned earlier the Payer object is the only one that doesn't tie directly back to a WePay endpoint. This is because the WePay API does not provide any way to search by a payer's information. All you can search by is a tokenized credit card ID.
238242

239243
However, if a payer comes to a platform's customer support and requests a refund, they likely don't know the token associated with their purchase. Storing payer information falls squarely onto the platform.

docs/index.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@ These actions include:
3232
- Withdrawal Lookups
3333
* Gather information about where a merchant's funds have been withdrawn to and when the next withdrawal should take place
3434
* This also includes being able to get reserve details for accounts that have money in reserve
35-
- Credit Card Token Lookup
35+
- Payment Method Lookups
3636
* Given a tokenized credit card, Kvasir can gather the original information used to create the token
37+
* Given a preapproval id, Kvasir can gather the original information used to create the token and provide a link where the conditions of the preapproval can be updated
38+
39+
.. figure:: images/KvasirLiveSnapshot.png
40+
:align: center
41+
:scale: 65%
42+
43+
A live snapshot of Kvasir in action
3744

3845
Tech Stack
3946
-----------

docs/sphinxext/wepay_docs.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
def setup(app):
9-
app.add_config_value("wepay_docs_home", "https://stage.wepay.com/developer/reference/", 'html')
9+
app.add_config_value("wepay_docs_home", "https://developer.wepay.com/api-calls/", 'html')
1010

1111
app.add_role("wepay", wepay_docs_role)
1212

@@ -34,7 +34,8 @@ def make_wepay_link(app, rawtext, endpoint, function, options):
3434

3535
# build external url
3636
# if no function is given, then it is the main endpoint, which is accessed by #lookup on the page
37-
ref = base + endpoint + "#" + function if function else base + endpoint + "#lookup"
37+
ref = "{0}{1}#{2}"
38+
ref = ref.format(base,endpoint,function) if function else ref.format(base,endpoint,"lookup")
3839

3940
# build the text that we will display instead of :wepay:`endpoint function`
4041

logs/README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ Logging Directory
22
------------------
33

44
This directory holds all of the log files generated by the application.
5+
6+
Logs are saved in JSON format. Express.js packages the requests and responses objects in a JSON format and writes them one per line. So each line is a different request or response. Looking at the status code lets you know what was and wasn't successful.

server.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,31 @@ if (!app_config.http_override) {
9999
var credentials = {key: privateKey, cert: certificate};
100100
}
101101

102-
// point the app to the static folder
103-
app.use('/static', express.static('static'));
104102
app.use(config.output.publicPath, express.static(config.output.path));
105103

106104

107105
var expressWinston = require("express-winston");
108106
var winston = require("winston");
107+
108+
// create a logger for our other log inputs
109+
// we remove the default Console logger and add our own to allow for more fine-tuned formatting
110+
winston.add(winston.transports.File, {
111+
level:"info",
112+
filename:"logs/log.log",
113+
timestamp: true,
114+
json: true,
115+
colorize: true
116+
});
117+
winston.remove(winston.transports.Console);
118+
winston.add(winston.transports.Console, {
119+
colorize: true,
120+
timstamp:true
121+
})
122+
109123
// define our logger
110-
app.use(expressWinston.logger({
111-
transports: [
124+
// the expressWinston.logger only logs the HTTP(s) requests and response
125+
// the expressWinston.errorLogger is at the bottom of the file. It has be
126+
var expressTransports = [
112127
new winston.transports.Console({
113128
colorize: true,
114129
timstamp:true,
@@ -120,13 +135,16 @@ app.use(expressWinston.logger({
120135
timestamp:true,
121136

122137
})
123-
],
138+
]
139+
app.use(expressWinston.logger({
140+
transports: expressTransports,
124141
meta: true, // optional: control whether you want to log the meta data about the request (default to true)
125142
expressFormat: true
126143
}));
127144

128-
129145
// use ejs for our template engine
146+
// point the app to the static folder
147+
app.use('/static', express.static('static'));
130148
app.set("view engine", "ejs");
131149

132150
/**
@@ -496,6 +514,20 @@ app.post("/preapproval/cancel", csrfProtection, function(req, res){
496514

497515
})
498516

517+
// define the error logger.
518+
// it uses the same transports as the normal logger, and will write all errors to a special file for easier access
519+
var error_transport = new winston.transports.File({
520+
filename: "logs/error.log",
521+
level: "error",
522+
json: true,
523+
timestamp:true
524+
});
525+
app.use(expressWinston.errorLogger({
526+
transports: [error_transport],
527+
meta: true, // optional: control whether you want to log the meta data about the request (default to true)
528+
expressFormat: true
529+
}));
530+
499531

500532
/**
501533
* Start the application

0 commit comments

Comments
 (0)