├── .dockerignore
├── .env
├── .gitignore
├── .nvmrc
├── Dockerfile
├── LICENSE
├── README.md
├── babel.config.js
├── docker-compose.yml
├── docker
└── nginx.conf
├── package-lock.json
├── package.json
├── public
├── Lithic-light-logo-mark.svg
├── browserconfig.xml
├── embedded.css
├── favicon.ico
├── icon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ └── safari-pinned-tab.svg
├── index.html
└── manifest.json
├── server
└── index.js
├── src
├── App.css
├── assets
│ ├── ApiKey.png
│ ├── Graphik-Regular.ttf
│ ├── Graphik-Semibold.ttf
│ ├── card.png
│ ├── cards.png
│ ├── funding.png
│ ├── home.png
│ ├── logo.svg
│ ├── transaction.png
│ └── transactions.png
├── components
│ ├── Card.vue
│ ├── CardGrid.vue
│ ├── CardModal.vue
│ ├── FundingModal.vue
│ ├── Info.vue
│ ├── List.vue
│ ├── Navbar.vue
│ ├── PageHeader.vue
│ └── Sidebar.vue
├── main.js
├── router
│ └── index.js
├── store
│ └── index.js
└── views
│ ├── ApiKeyPage.vue
│ ├── CardPage.vue
│ ├── CardsPage.vue
│ ├── FundingPage.vue
│ ├── HomePage.vue
│ ├── TransactionPage.vue
│ ├── TransactionsPage.vue
│ └── index.vue
└── vue.config.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/dist
3 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | VUE_APP_API=http://localhost:3000
2 | VUE_APP_API_KEY=
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
24 | .prettierrc.json
25 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.15
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14.15 as node-stage
2 | ARG VUE_APP_API
3 | ARG VUE_APP_API_KEY
4 | ENV VUE_APP_API $VUE_APP_API
5 | ENV VUE_APP_API_KEY $VUE_APP_API_KEY
6 | WORKDIR /app
7 | COPY package*.json ./
8 | RUN npm install
9 | COPY ./ .
10 | RUN npm run build
11 |
12 | FROM nginx as nginx-stage
13 | RUN mkdir /app
14 | COPY --from=node-stage /app/dist /app
15 | COPY docker/nginx.conf /etc/nginx/nginx.conf
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Pay with Privacy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Developer API Demo
6 | A simple web app that showcases Lithic's [Developer API](https://docs.lithic.com) implemented with Vue and Express. Download the project and run it locally or check it out at [https://demo.lithic.com/](https://demo.lithic.com/)
7 |
8 | The Lithic developer API provides a predictable and programmatic interface to create and configure virtual cards. Get real-time payment data, programmatically issue cards, configure card permissions, and set spend limits all through the API.
9 |
10 | ## Overview
11 | The project is a simplified recreation of the main [Lithic](https://lithic.com) site in Vue with an Express backend. The right sidebar prints every API call the project makes to the Developer API to illustrate what is going on for every page. This demo covers the following endpoints:
12 | | Endpoints | Description |
13 | | -------------- | ----------- |
14 | | /card | Create, update, and fetch virtual cards with limits, vendor locks, and more. |
15 | | /embed/card | To comply with Payment Card Industry Data Security Standards (PCI DSS), retrieving a card's PAN number, expiration date, and CVV requires rendering an iframe, which we demonstrate here. Learn more about how we stay PCI compliant in our [documentation](https://docs.lithic.com/pci-compliance.html) |
16 | | /transaction | Fetch transactions from all your virtual cards. |
17 | | /fundingsource | Create and fetch funding cards and accounts. |
18 | | /simulate | While using our sandbox, use our `/simulate` endpoint to create transactions and modify their states to further emulate real-world interaction. |
19 |
20 | ## Running the Project
21 | You can run the project locally or in Docker containers. To start, first update the `.env` file with your [sandbox API key](https://lithic.com/account). This step is optional but will persist your api-key.
22 |
23 | ### Running Locally
24 |
25 | Install dependencies with npm:
26 | ```
27 | npm install
28 | ```
29 | The demo uses both Vue and Express which can be run locally using:
30 | ```
31 | npm run serve
32 | ```
33 | Alternatively, you can run in separate windows
34 | ```
35 | npm run serve:app
36 | ```
37 | ```
38 | npm run serve:server
39 | ```
40 | to run the two processes separately.
41 |
42 | ### Running in Docker
43 | Launch the docker containers using docker-compose.
44 | ```
45 | docker-compose up -d
46 | ```
47 |
48 | ## Images
49 |
50 | ### Home Page
51 | 
52 |
53 | ### Cards Page
54 | 
55 |
56 | ### Card Page
57 | 
58 |
59 | ### Transactions Page
60 | 
61 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | frontend:
5 | build:
6 | context: ./
7 | dockerfile: Dockerfile
8 | target: nginx-stage
9 | args:
10 | - VUE_APP_API=$VUE_APP_API
11 | - VUE_APP_API_KEY=$VUE_APP_API_KEY
12 | ports: ["8080:80"]
13 |
14 | backend:
15 | build:
16 | context: ./
17 | dockerfile: Dockerfile
18 | target: node-stage
19 | args:
20 | - VUE_APP_API=$VUE_APP_API
21 | - VUE_APP_API_KEY=$VUE_APP_API_KEY
22 | ports: ["3000:3000"]
23 | expose:
24 | - "3000"
25 | command: 'node server'
26 |
--------------------------------------------------------------------------------
/docker/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes 1;
3 | error_log /var/log/nginx/error.log warn;
4 | pid /var/run/nginx.pid;
5 | events {
6 | worker_connections 1024;
7 | }
8 | http {
9 | include /etc/nginx/mime.types;
10 | default_type application/octet-stream;
11 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
12 | '$status $body_bytes_sent "$http_referer" '
13 | '"$http_user_agent" "$http_x_forwarded_for"';
14 | access_log /var/log/nginx/access.log main;
15 | sendfile on;
16 | keepalive_timeout 65;
17 | server {
18 | listen 80;
19 | server_name localhost;
20 | location / {
21 | root /app;
22 | index index.html;
23 | try_files $uri $uri/ /index.html;
24 | }
25 | error_page 500 502 503 504 /50x.html;
26 | location = /50x.html {
27 | root /usr/share/nginx/html;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "privacy-api-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "npm-run-all -p serve:**",
7 | "serve:app": "vue-cli-service serve",
8 | "serve:server": "node server",
9 | "build": "vue-cli-service build",
10 | "lint": "vue-cli-service lint",
11 | "lint:fix": "vue-cli-service lint --fix"
12 | },
13 | "dependencies": {
14 | "axios": "^0.21.1",
15 | "body-parser": "^1.19.0",
16 | "core-js": "^3.6.5",
17 | "cors": "^2.8.5",
18 | "crypto-js": "^4.0.0",
19 | "dotenv": "^8.2.0",
20 | "express": "^4.17.1",
21 | "install": "^0.13.0",
22 | "lodash": "^4.17.19",
23 | "npm": "^6.14.7",
24 | "npm-run-all": "^4.1.5",
25 | "vue": "^2.6.11",
26 | "vue-chat-scroll": "^1.4.0",
27 | "vue-js-modal": "^2.0.0-rc.6",
28 | "vue-js-toggle-button": "^1.3.3",
29 | "vue-json-pretty": "^1.9.4",
30 | "vue-router": "^3.3.4",
31 | "vue-spinner": "^1.0.4",
32 | "vue-toastr": "^2.1.2",
33 | "vuex": "^3.5.1"
34 | },
35 | "devDependencies": {
36 | "@vue/cli-plugin-babel": "~4.4.0",
37 | "@vue/cli-plugin-eslint": "~4.4.0",
38 | "@vue/cli-service": "~4.4.0",
39 | "babel-eslint": "^10.1.0",
40 | "eslint": "^6.7.2",
41 | "eslint-config-prettier": "^6.11.0",
42 | "eslint-plugin-prettier": "^3.1.4",
43 | "eslint-plugin-vue": "^6.2.2",
44 | "sass": "^1.26.10",
45 | "sass-loader": "^9.0.2",
46 | "vue-template-compiler": "^2.6.11"
47 | },
48 | "eslintConfig": {
49 | "root": true,
50 | "env": {
51 | "node": true
52 | },
53 | "extends": [
54 | "plugin:vue/essential",
55 | "plugin:prettier/recommended",
56 | "eslint:recommended"
57 | ],
58 | "parserOptions": {
59 | "parser": "babel-eslint"
60 | },
61 | "rules": {}
62 | },
63 | "prettier": {},
64 | "browserslist": [
65 | "> 1%",
66 | "last 2 versions",
67 | "not dead"
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/public/Lithic-light-logo-mark.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #000000
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/embedded.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2family=Roboto+Mono:wght@400;600&display=swap");
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
7 | #card {
8 | color: white;
9 | display: grid;
10 | column-gap: 20px;
11 | row-gap: 10px;
12 | grid-template-columns: min-content 1fr;
13 | grid-template-rows: 1fr min-content;
14 | font-family: "Roboto Mono", sans-serif;
15 | height: 100%;
16 | }
17 |
18 | #pan {
19 | grid-column: 1 / 3;
20 | }
21 |
22 | #expiry {
23 | min-width: 0;
24 | white-space: nowrap;
25 | }
26 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/favicon.ico
--------------------------------------------------------------------------------
/public/icon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/icon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/icon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/icon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/mstile-144x144.png
--------------------------------------------------------------------------------
/public/icon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/icon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/mstile-310x150.png
--------------------------------------------------------------------------------
/public/icon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/mstile-310x310.png
--------------------------------------------------------------------------------
/public/icon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/public/icon/mstile-70x70.png
--------------------------------------------------------------------------------
/public/icon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 | Lithic API Demo
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "icons": [
4 | {
5 | "src": "./icon/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | },
9 | {
10 | "src": "./icon/android-chrome-512x512.png",
11 | "sizes": "512x512",
12 | "type": "image/png"
13 | }
14 | ],
15 | "theme_color": "#ffffff",
16 | "background_color": "#ffffff",
17 | "display": "standalone"
18 | }
19 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const axios = require("axios");
3 | const CryptoJS = require("crypto-js");
4 | const bodyParser = require("body-parser");
5 | const cors = require("cors");
6 |
7 | require("dotenv").config();
8 |
9 | const app = express();
10 | const port = 3000;
11 |
12 | app.use(cors());
13 | app.use(bodyParser.json());
14 | app.use(bodyParser.urlencoded({ extended: true }));
15 | app.use(express.static("public"));
16 |
17 | const apiKey = process.env.VUE_APP_API_KEY;
18 | const apiBaseURL = "https://sandbox.lithic.com/v1";
19 |
20 | const callApi = async (request, response) => {
21 | const { method, path, query, body, headers } = request;
22 | try {
23 | const req = {
24 | headers: {
25 | Authorization: headers.authorization || `api-key ${apiKey}`,
26 | "Content-Type": "application/json",
27 | },
28 | method: method.toLowerCase(),
29 | baseURL: apiBaseURL,
30 | url: path,
31 | };
32 |
33 | if (method === "GET") {
34 | req.params = query;
35 | } else {
36 | req.data = body;
37 | }
38 |
39 | const { data } = await axios(req);
40 |
41 | response.status(200);
42 | response.send({ ...data, displayURL: apiBaseURL + path });
43 | } catch (err) {
44 | response.status(err.response.status);
45 | response.send(err.response.data);
46 | }
47 | };
48 |
49 | // Learn why this is necessary at https://docs.lithic.com/pci-compliance.html
50 | const hostedCard = (request, response) => {
51 | const { headers, query } = request;
52 | const { card_token } = query;
53 | const embed_request_json = JSON.stringify({
54 | css: `${process.env.VUE_APP_API}/embedded.css`,
55 | token: card_token,
56 | });
57 |
58 | const passedApiKey =
59 | headers.authorization && headers.authorization.replace("api-key ", "");
60 | const embed_request = Buffer.from(embed_request_json).toString("base64");
61 | const hmac = CryptoJS.enc.Base64.stringify(
62 | CryptoJS.HmacSHA256(embed_request_json, passedApiKey || apiKey)
63 | );
64 |
65 | const displayURL = `${apiBaseURL}/embed/card`;
66 | const url = `${displayURL}?embed_request=${embed_request}&hmac=${hmac}`;
67 |
68 | response.send({ displayURL, url, params: { embed_request, hmac } });
69 | };
70 |
71 | app.all("/cards", callApi);
72 | app.all("/fundingsource*", callApi);
73 | app.get("/transaction*", callApi);
74 | app.post("/simulate/*", callApi);
75 | app.get("/embed/card", hostedCard);
76 |
77 | app.listen(port);
78 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Roboto+Mono:wght@400;600&display=swap");
2 |
3 | @font-face {
4 | font-family: "Graphik";
5 | src: url("~@/assets/Graphik-Regular.ttf") format("truetype");
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | @font-face {
11 | font-family: "Graphik";
12 | src: url("~@/assets/Graphik-Semibold.ttf") format("truetype");
13 | font-weight: bold;
14 | font-style: normal;
15 | }
16 |
17 | h1, h2, h3, h4 {
18 | font-family: "Graphik";
19 | }
20 |
21 | body {
22 | margin: 0;
23 | --background:white;
24 | --color: black;
25 | --accent: #232323;
26 | --light: #f0eeeb;
27 | --logo: invert(9%) sepia(10%) saturate(418%) hue-rotate(22deg) brightness(98%) contrast(89%);
28 | --dark: #232323;
29 | --dark-accent: rgb(76, 75, 72);
30 | --embed: #232323;
31 | }
32 |
33 | .dark {
34 | --color: white;
35 | --background: #232320;
36 | --light: #4c4b48;
37 | --accent: white;
38 | --inv-color: black;
39 | --logo: brightness(0) invert(1);
40 | --dark: #1b1b1a;
41 | --dark-accent: #3a3937;
42 | --embed: rgba(242, 244, 250, 0.2);
43 | }
44 |
45 | div,
46 | a,
47 | button {
48 | font-family: "Lato";
49 | font-size: 14px;
50 | color: var(--color, black);
51 | }
52 |
53 | h1, h2, h3, h4, h5, h6 {
54 | color: var(--color, black);
55 | }
56 |
57 | button, .button {
58 | text-decoration: none;
59 | color: var(--color, black);
60 | text-align: center;
61 | padding: 10px 20px;
62 | background-color: rgba(196, 196, 196, 0.2);
63 | border: none;
64 | transition: background-color 0.3s;
65 | border-radius: 32px;
66 | cursor: pointer;
67 | }
68 |
69 | button:hover, .button:hover {
70 | background-color: rgba(196, 196, 196, 0.4);
71 | }
72 |
73 | button:active, .button:active {
74 | background-color: rgba(196, 196, 196, 0.1);
75 | }
76 |
77 | @keyframes fadein {
78 | from {
79 | opacity: 0;
80 | }
81 | to {
82 | opacity: 1;
83 | }
84 | }
85 | @keyframes fadeout {
86 | from {
87 | opacity: 1;
88 | }
89 | to {
90 | opacity: 0;
91 | }
92 | }
93 |
94 | .v-spinner {
95 | margin: 50px 0;
96 | text-align: center;
97 | }
98 |
99 | .v-spinner > .v-beat {
100 | background-color: var(--accent, black) !important;
101 | }
102 |
--------------------------------------------------------------------------------
/src/assets/ApiKey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/ApiKey.png
--------------------------------------------------------------------------------
/src/assets/Graphik-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/Graphik-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/Graphik-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/Graphik-Semibold.ttf
--------------------------------------------------------------------------------
/src/assets/card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/card.png
--------------------------------------------------------------------------------
/src/assets/cards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/cards.png
--------------------------------------------------------------------------------
/src/assets/funding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/funding.png
--------------------------------------------------------------------------------
/src/assets/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/home.png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/transaction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/transaction.png
--------------------------------------------------------------------------------
/src/assets/transactions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lithic-com/api-demo/03bd3904a160c8836e6449e31ec5d6ca7fd91c55/src/assets/transactions.png
--------------------------------------------------------------------------------
/src/components/Card.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ url }}
4 |
5 |
12 |
13 |
14 |
{{ topLeft || "" }}
15 |
{{ topRight || "" }}
16 |
17 |
18 |
{{ middleLeft || "" }}
19 |
20 |
21 |
22 |
{{ bottomLeft || "" }}
23 |
{{ bottomRight || "" }}
24 |
25 |
26 |
27 |
28 |
29 |
43 |
44 |
99 |
--------------------------------------------------------------------------------
/src/components/CardGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
No Cards
5 |
6 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
33 |
34 |
55 |
--------------------------------------------------------------------------------
/src/components/CardModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ card && card.token ? "Update Card" : "Create Card" }}
4 |
Memo:
5 |
6 |
Type:
7 |
12 |
Spend limit:
13 |
14 |
Spend limit duration:
15 |
21 |
State:
22 |
27 |
28 |
31 |
32 |
33 |
34 |
81 |
82 |
103 |
--------------------------------------------------------------------------------
/src/components/FundingModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Add a Funding Source
4 |
Account Name (Optional):
5 |
6 |
Account Number:
7 |
8 |
Routing Number:
9 |
10 |
11 |
12 |
13 |
14 |
15 |
44 |
45 |
66 |
--------------------------------------------------------------------------------
/src/components/Info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
30 |
--------------------------------------------------------------------------------
/src/components/List.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
No items
5 |
11 |
12 | {{ new Date(item.created).toString().slice(4, 16) }}
13 |
14 | {{ item.merchant ? item.merchant.descriptor : item.result }}
15 | {{ (item.status || item.type).toLowerCase() }}
16 | ${{ (item.amount / 100).toFixed(2) }}
17 |
18 |
19 |
20 |
21 |
31 |
32 |
71 |
--------------------------------------------------------------------------------
/src/components/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
30 |
31 |
69 |
--------------------------------------------------------------------------------
/src/components/PageHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
24 |
25 |
59 |
--------------------------------------------------------------------------------
/src/components/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
35 |
36 |
89 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueChatScroll from "vue-chat-scroll";
3 | import VueToastr from "vue-toastr";
4 | import VModal from "vue-js-modal";
5 | import ToggleButton from "vue-js-toggle-button";
6 |
7 | import App from "./views";
8 | import router from "./router";
9 | import store from "./store";
10 | import "./App.css";
11 |
12 | Vue.config.productionTip = false;
13 |
14 | Vue.use(VueChatScroll);
15 | Vue.use(VModal);
16 | Vue.use(ToggleButton);
17 | Vue.use(VueToastr, {
18 | defaultPosition: "toast-bottom-left",
19 | });
20 |
21 | new Vue({
22 | el: "#app",
23 | router,
24 | store,
25 | template: "",
26 | components: { App },
27 | });
28 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueRouter from "vue-router";
3 | import HomePage from "@/views/HomePage";
4 | import CardPage from "@/views/CardPage";
5 | import CardsPage from "@/views/CardsPage";
6 | import TransactionPage from "@/views/TransactionPage";
7 | import TransactionsPage from "@/views/TransactionsPage";
8 | import FundingPage from "@/views/FundingPage";
9 | import ApiKeyPage from "@/views/ApiKeyPage";
10 | import store from "@/store/index.js";
11 |
12 | Vue.use(VueRouter);
13 |
14 | const Router = new VueRouter({
15 | mode: "history",
16 | base: process.env.BASE_URL,
17 | routes: [
18 | {
19 | path: "/",
20 | name: "Home",
21 | component: HomePage,
22 | },
23 | {
24 | path: "/card/:card_token",
25 | name: "Card",
26 | component: CardPage,
27 | },
28 | {
29 | path: "/card",
30 | name: "Cards",
31 | component: CardsPage,
32 | },
33 | {
34 | path: "/transaction/:transaction_token",
35 | name: "Transaction",
36 | component: TransactionPage,
37 | },
38 | {
39 | path: "/transaction",
40 | name: "Transactions",
41 | component: TransactionsPage,
42 | },
43 | {
44 | path: "/fundingsource*",
45 | name: "Funding",
46 | component: FundingPage,
47 | },
48 | {
49 | path: "/apiKey",
50 | name: "API Key",
51 | component: ApiKeyPage,
52 | },
53 | ],
54 | });
55 |
56 | Router.beforeEach((to, from, next) => {
57 | if (
58 | !store.state.apiKey &&
59 | !process.env.VUE_APP_API_KEY &&
60 | to.path !== "/apiKey" &&
61 | !to.query.apiKey &&
62 | !from.query.apiKey
63 | ) {
64 | next({ path: "/apiKey" });
65 | } else {
66 | if (from.query.apiKey && !to.query.apiKey) {
67 | next({ path: to.path, query: from.query, replace: true });
68 | } else {
69 | store.commit("setRequests", to);
70 | next();
71 | }
72 | }
73 | });
74 |
75 | export default Router;
76 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 | import axios from "axios";
4 | import router from "@/router";
5 |
6 | Vue.use(Vuex);
7 |
8 | const baseURL = process.env.VUE_APP_API;
9 |
10 | export default new Vuex.Store({
11 | state: { requests: [], cards: [], apiKey: null, darkMode: false },
12 | actions: {
13 | async apiRequest(
14 | { commit, state },
15 | { url, method = "get", logging = true, data: passedData }
16 | ) {
17 | try {
18 | const paramApiKey = router.history.current.query.apiKey;
19 | const params = { ...passedData, ...router.currentRoute.params };
20 | delete params.pathMatch;
21 | const headers = { "Content-Type": "application/json" };
22 |
23 | if (state.apiKey || paramApiKey) {
24 | headers["Authorization"] = `api-key ${state.apiKey || paramApiKey}`;
25 | }
26 |
27 | const req = {
28 | headers,
29 | method,
30 | baseURL,
31 | url,
32 | };
33 |
34 | if (method === "get") {
35 | req.params = params;
36 | } else {
37 | req.data = passedData;
38 | }
39 |
40 | const res = await axios(req);
41 | const { data, displayURL, ...otherData } = res.data;
42 |
43 | if (logging) {
44 | commit("setRequests", {
45 | displayURL,
46 | method,
47 | params: req.params && (otherData.params || params),
48 | data: !req.params && passedData,
49 | });
50 | }
51 |
52 | return data || otherData;
53 | } catch (err) {
54 | window.toastr.e(err.response.data.message);
55 | }
56 | },
57 | },
58 | mutations: {
59 | setApiKey: (state, request) => {
60 | state.apiKey = request;
61 | },
62 | setRequests: (state, request) => {
63 | state.requests = [...state.requests, request];
64 | },
65 | toggleDarkMode: (state) => {
66 | state.darkMode = !state.darkMode;
67 | },
68 | },
69 | });
70 |
--------------------------------------------------------------------------------
/src/views/ApiKeyPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Sandbox API Key
4 |
31 |
32 |
33 |
34 |
63 |
64 |
116 |
--------------------------------------------------------------------------------
/src/views/CardPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Card Schema
12 |
13 |
14 |
15 |
Hosted Card UI
16 |
17 |
18 |
19 | Due to security constraints, a card’s expiration date, pan, and cvv can
20 | only be displayed through a hosted iframe
21 |
22 |
23 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
142 |
143 |
198 |
--------------------------------------------------------------------------------
/src/views/CardsPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
60 |
61 |
73 |
--------------------------------------------------------------------------------
/src/views/FundingPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
No funding sources
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
60 |
61 |
73 |
--------------------------------------------------------------------------------
/src/views/HomePage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Transactions
20 |
21 |
22 |
23 |
24 | See All Cards
25 |
26 |
27 | See All Transactions
28 |
29 |
30 |
31 |
32 |
33 |
81 |
82 |
134 |
--------------------------------------------------------------------------------
/src/views/TransactionPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Transaction Schema
8 |
9 |
10 |
11 |
Merchant Data
12 |
13 |
14 | {{ transaction.merchant.descriptor }}
15 |
16 |
17 | {{ transaction.merchant.state }},{{ transaction.merchant.country }}
18 |
19 |
{{ transaction.merchant.mcc }}
20 |
21 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
92 |
93 |
142 |
--------------------------------------------------------------------------------
/src/views/TransactionsPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
73 |
74 |
108 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
35 |
36 |
61 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | runtimeCompiler: true,
3 | };
4 |
--------------------------------------------------------------------------------