├── ruby ├── .ruby-version ├── .env.example ├── Gemfile ├── Dockerfile ├── Gemfile.lock └── app.rb ├── node ├── .dockerignore ├── .env.example ├── .eslintrc.js ├── Dockerfile ├── package.json └── server.js ├── frontend ├── .dockerignore ├── src │ ├── index.css │ ├── index.js │ ├── helpers │ │ └── utils.js │ ├── setupProxy.js │ ├── logo.svg │ ├── App.js │ ├── Components │ │ ├── ConnectLink │ │ │ └── ConnectLink.js │ │ ├── MovementList │ │ │ └── MovementList.js │ │ └── AccountList │ │ │ └── AccountList.js │ └── imagotipo.svg ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── isotipo.png │ ├── manifest.json │ └── index.html ├── .eslintrc.js ├── .env.example ├── Dockerfile ├── craco.config.js ├── .gitignore ├── tailwind.config.js └── package.json ├── python ├── requirements.txt ├── .env.example ├── Dockerfile └── app.py ├── _media └── fintoc.png ├── .gitignore ├── .env.example ├── Makefile ├── docker-compose.yml └── README.md /ruby/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.3 2 | -------------------------------------------------------------------------------- /node/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | flask==1.1.4 2 | fintoc==0.3.1 3 | python-dotenv==0.17.1 -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /_media/fintoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintoc-com/quickstart/HEAD/_media/fintoc.png -------------------------------------------------------------------------------- /node/.env.example: -------------------------------------------------------------------------------- 1 | # Get your Secret Key (sk_) from https://app.fintoc.com/api-keys 2 | SECRET_KEY= -------------------------------------------------------------------------------- /ruby/.env.example: -------------------------------------------------------------------------------- 1 | # Get your Secret Key (sk_) from https://app.fintoc.com/api-keys 2 | SECRET_KEY= -------------------------------------------------------------------------------- /python/.env.example: -------------------------------------------------------------------------------- 1 | # Get your Secret Key (sk_) from https://app.fintoc.com/api-keys 2 | SECRET_KEY= -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'airbnb-base', 3 | //disable: 'no-console', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintoc-com/quickstart/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/isotipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintoc-com/quickstart/HEAD/frontend/public/isotipo.png -------------------------------------------------------------------------------- /node/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'airbnb-base', 3 | // disable: 'no-console', 4 | }; 5 | -------------------------------------------------------------------------------- /ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'dotenv', '~> 2.7.6' 6 | gem 'fintoc', '~> 0.1.0' 7 | gem 'sinatra', '~> 2.1.0' 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #.env 2 | .env 3 | 4 | # Dependency directories 5 | node_modules/ 6 | 7 | # Jetbrain 8 | .idea 9 | 10 | # VSCode 11 | .vscode 12 | 13 | # OSX 14 | .DS_Store -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # Get your Public Key (pk_) from https://app.fintoc.com/api-keys 2 | REACT_APP_PUBLIC_KEY= 3 | 4 | # URL to receive your link_token from widget 5 | REACT_APP_WEBHOOK_URL= 6 | -------------------------------------------------------------------------------- /node/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VER=14.17.0 2 | FROM node:${NODE_VER} 3 | 4 | WORKDIR /opt/app 5 | 6 | COPY . . 7 | 8 | RUN npm install 9 | 10 | EXPOSE 5000 11 | 12 | ENTRYPOINT ["npm"] 13 | CMD ["start"] -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VER=14.17.0 2 | FROM node:${NODE_VER} 3 | 4 | WORKDIR /opt/app 5 | 6 | COPY . . 7 | 8 | RUN npm install 9 | 10 | EXPOSE 3000 11 | 12 | ENTRYPOINT ["npm"] 13 | CMD ["start"] 14 | -------------------------------------------------------------------------------- /ruby/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUBY_VER=2.7.3 2 | FROM ruby:${RUBY_VER} 3 | 4 | WORKDIR /opt/app 5 | 6 | COPY . . 7 | 8 | RUN bundle install 9 | 10 | EXPOSE 5000 11 | 12 | ENTRYPOINT ["ruby"] 13 | CMD ["app.rb", "-o", "0.0.0.0"] 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root'), 11 | ); 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Get your Secret Key (sk_) from https://app.fintoc.com/api-keys 2 | SECRET_KEY= 3 | # Get your Public Key (pk_) from https://app.fintoc.com/api-keys 4 | REACT_APP_PUBLIC_KEY= 5 | # URL where Fintoc will Post a link_token (more: https://docs.fintoc.com/docs/integration) 6 | REACT_APP_WEBHOOK_URL= 7 | -------------------------------------------------------------------------------- /frontend/src/helpers/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export function formatDate(date) { 4 | return moment(date, 'YYYY-MM-DDTHH:mm:ss.sssZ').format('DD-MMM-YYYY'); 5 | } 6 | 7 | export function formatAmount(amount) { 8 | return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/craco.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable global-require */ 3 | module.exports = { 4 | style: { 5 | postcss: { 6 | plugins: [ 7 | require('tailwindcss'), 8 | require('autoprefixer'), 9 | ], 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /python/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VER=3.6.1 2 | FROM python:${PYTHON_VER} 3 | 4 | WORKDIR /opt/app 5 | 6 | COPY . . 7 | 8 | RUN pip3 install -r requirements.txt 9 | 10 | EXPOSE 5000 11 | 12 | ENV FLASK_APP=/opt/app/python/app.py 13 | 14 | ENTRYPOINT ["python"] 15 | CMD ["-m", "flask", "run", "--host=0.0.0.0", "--port=5000"] -------------------------------------------------------------------------------- /frontend/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const createProxyMiddleware = require('http-proxy-middleware'); 2 | 3 | module.exports = (app) => { 4 | app.use( 5 | '/api', 6 | createProxyMiddleware({ 7 | target: process.env.REACT_APP_BACKEND_HOST || 'http://localhost:5000', 8 | changeOrigin: true, 9 | }), 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: { 6 | colors: { 7 | darkBlue: '#272634', 8 | fintocBlue: '#475FF1', 9 | fintocLight: '#6A8DF9', 10 | }, 11 | }, 12 | }, 13 | variants: { 14 | extend: {}, 15 | }, 16 | plugins: [], 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fintoc Sample App", 3 | "name": "Fintoc Sample App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "isotipo.png", 12 | "type": "image/png", 13 | "sizes": "20x24" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fintoc-quickstart", 3 | "version": "1.0.0", 4 | "description": "Fintoc quickstart with React and Express", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "server": "nodemon server.js", 9 | "client": "cd ../frontend && npm start", 10 | "install-client": "cd ../frontend && npm install", 11 | "dev": "concurrently \"npm run server\" \"npm run client\"" 12 | }, 13 | "author": "Paulina Araya", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.19.0", 17 | "concurrently": "^6.0.2", 18 | "dotenv": "^8.5.1", 19 | "eslint": "^7.25.0", 20 | "eslint-config-airbnb-base": "^14.2.1", 21 | "eslint-plugin-import": "^2.22.1", 22 | "express": "^4.17.1", 23 | "fintoc": "^0.1.0", 24 | "moment": "^2.29.1" 25 | }, 26 | "devDependencies": { 27 | "nodemon": "^2.0.7" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import logo from './imagotipo.svg'; 3 | import AccountList from './Components/AccountList/AccountList'; 4 | import ConnectLink from './Components/ConnectLink/ConnectLink'; 5 | import MovementList from './Components/MovementList/MovementList'; 6 | 7 | function App() { 8 | const [linkId, setLinkId] = useState(''); 9 | const [accountId, setAccountId] = useState(''); 10 | return ( 11 |
12 |
13 | 14 | logo 15 | 16 |
17 | {!linkId && } 18 | {linkId && !accountId 19 | && } 20 | {linkId && accountId 21 | && } 22 | 23 |
24 | ); 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | language := ruby 2 | 3 | help: 4 | @echo "Usage: make COMMAND [OPTIONS]" 5 | @echo 6 | @echo "Options:" 7 | @echo " language=[language] - The language can be any of the supported languages such as: node, ruby or python." 8 | @echo 9 | @echo "Commands:" 10 | @echo " start\t\tStart the containers (and builds them if they don't exist)." 11 | @echo " build\t\tBuild the Docker images." 12 | @echo " destroy\tDestroy the containers." 13 | @echo " stop\t\tStop the containers." 14 | @echo " restart\tRestart the containers." 15 | @echo " logs\t\tShow container logs." 16 | .PHONY: help 17 | 18 | start: ## Start the containers 19 | @REACT_APP_BACKEND_HOST=http://$(language):5000 \ 20 | docker-compose up --remove-orphans --detach $(language) frontend 21 | .PHONY: start 22 | 23 | build: ## Build the containers 24 | docker-compose build --no-cache 25 | .PHONY: build 26 | 27 | destroy: ## Destroy the containers 28 | @docker-compose down 29 | .PHONY: destroy 30 | 31 | stop: ## Stop the containers 32 | @docker-compose stop 33 | .PHONY: stop 34 | 35 | restart: ## Restart the containers 36 | @make -s destroy 37 | @make -s start 38 | .PHONY: restart 39 | 40 | logs: ## Show containers logs 41 | @docker-compose logs 42 | .PHONY: logs 43 | -------------------------------------------------------------------------------- /ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.7.0) 5 | public_suffix (>= 2.0.2, < 5.0) 6 | domain_name (0.5.20190701) 7 | unf (>= 0.0.5, < 1.0.0) 8 | dotenv (2.7.6) 9 | ffi (1.15.0) 10 | ffi-compiler (1.0.1) 11 | ffi (>= 1.0.0) 12 | rake 13 | fintoc (0.1.0) 14 | http 15 | tabulate 16 | http (5.0.0) 17 | addressable (~> 2.3) 18 | http-cookie (~> 1.0) 19 | http-form_data (~> 2.2) 20 | llhttp-ffi (~> 0.0.1) 21 | http-cookie (1.0.3) 22 | domain_name (~> 0.5) 23 | http-form_data (2.3.0) 24 | llhttp-ffi (0.0.1) 25 | ffi-compiler (~> 1.0) 26 | rake (~> 13.0) 27 | mustermann (1.1.1) 28 | ruby2_keywords (~> 0.0.1) 29 | public_suffix (4.0.6) 30 | rack (2.2.3) 31 | rack-protection (2.1.0) 32 | rack 33 | rake (13.0.3) 34 | ruby2_keywords (0.0.4) 35 | sinatra (2.1.0) 36 | mustermann (~> 1.0) 37 | rack (~> 2.2) 38 | rack-protection (= 2.1.0) 39 | tilt (~> 2.0) 40 | tabulate (0.1.2) 41 | tilt (2.0.10) 42 | unf (0.1.4) 43 | unf_ext 44 | unf_ext (0.0.7.7) 45 | 46 | PLATFORMS 47 | ruby 48 | x86_64-darwin-20 49 | 50 | DEPENDENCIES 51 | dotenv (~> 2.7.6) 52 | fintoc (~> 0.1.0) 53 | sinatra (~> 2.1.0) 54 | 55 | BUNDLED WITH 56 | 2.2.15 57 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^6.1.2", 7 | "@testing-library/jest-dom": "^5.12.0", 8 | "@testing-library/react": "^11.2.6", 9 | "@testing-library/user-event": "^12.8.3", 10 | "eslint": "^7.25.0", 11 | "eslint-config-airbnb-base": "^14.2.1", 12 | "eslint-plugin-import": "^2.22.1", 13 | "http-proxy-middleware": "^0.19.1", 14 | "moment": "^2.29.1", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-script-hook": "^1.3.0", 18 | "react-scripts": "4.0.3", 19 | "web-vitals": "^1.1.1" 20 | }, 21 | "scripts": { 22 | "start": "craco start", 23 | "build": "craco build", 24 | "test": "craco test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "proxy": "http://localhost:5000", 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "autoprefixer": "^9.8.6", 48 | "postcss": "^7.0.35", 49 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.1.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ruby/app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dotenv' 4 | require 'sinatra' 5 | require 'fintoc' 6 | Dotenv.load 7 | 8 | set :port, 5000 9 | set :protection, :except => [:json_csrf] 10 | 11 | link_token = '' 12 | 13 | fintoc = Fintoc::Client.new(ENV['SECRET_KEY']) 14 | 15 | get '/api/accounts' do 16 | link = fintoc.get_link(link_token) 17 | accounts = link.find_all(type: 'checking_account') 18 | content_type :json 19 | accounts.map do |account| 20 | { balance: { available: account.balance.available, current: account.balance.current }, 21 | id: account.id, 22 | name: account.name, 23 | holderName: account.holder_name, 24 | currency: account.currency } 25 | end.to_json 26 | end 27 | 28 | get '/api/accounts/:account_id/movements' do 29 | start_of_month = Date.new(Date.today.year, Date.today.month, 1) 30 | link = fintoc.get_link(link_token) 31 | account = link.find(id: params[:account_id]) 32 | 33 | content_type :json 34 | last_month_movements = account.get_movements(since: start_of_month.to_s) 35 | last_month_movements.to_a.map do |movement| 36 | { id: movement.id, 37 | postDate: movement.post_date, 38 | currency: movement.currency, 39 | amount: movement.amount, 40 | description: movement.description } 41 | end.to_json 42 | end 43 | 44 | post '/api/link_token' do 45 | request.body.rewind 46 | link_token = (JSON.parse request.body.read)['data']['link_token'] 47 | 'Post request to /api/link_token' 48 | end 49 | -------------------------------------------------------------------------------- /node/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const dotenv = require('dotenv'); 4 | const Fintoc = require('fintoc'); 5 | const moment = require('moment'); 6 | 7 | dotenv.config(); 8 | let linkToken = ''; 9 | 10 | const fintoc = new Fintoc(process.env.SECRET_KEY); 11 | const app = express(); 12 | app.use( 13 | bodyParser.urlencoded({ 14 | extended: false, 15 | }), 16 | ); 17 | app.use(bodyParser.json()); 18 | 19 | app.get('/api/accounts', async (req, res) => { 20 | try { 21 | const link = await fintoc.getLink(linkToken); 22 | const accounts = link.findAll({ type_: 'checking_account' }); 23 | res.json(accounts); 24 | } catch (error) { 25 | res.status(400); 26 | res.json(error); 27 | } 28 | }); 29 | 30 | app.get('/api/accounts/:accountId/movements', async (req, res) => { 31 | try { 32 | const startOfMonth = moment().startOf('month').format('YYYY-MM-DD'); 33 | const link = await fintoc.getLink(linkToken); 34 | const account = link.find({ id_: req.params.accountId }); 35 | const lastMonthMovements = await account.getMovements({ since: startOfMonth }); 36 | res.json(lastMonthMovements); 37 | } catch (error) { 38 | res.status(400); 39 | res.json(error); 40 | } 41 | }); 42 | 43 | app.post('/api/link_token', (req, res) => { 44 | linkToken = req.body.data.link_token; 45 | res.send('Post request to /api/link_token'); 46 | }); 47 | 48 | const port = process.env.PORT || 5000; 49 | 50 | app.listen(port, () => console.log(`Server started on port ${port}`)); 51 | -------------------------------------------------------------------------------- /python/app.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from fintoc import Client 3 | from flask import Flask, request, jsonify 4 | import os 5 | 6 | app = Flask(__name__) 7 | 8 | client = Client(os.environ['SECRET_KEY']) 9 | link_token = None 10 | 11 | 12 | @app.route('/api/link_token', methods=['POST']) 13 | def set_link_token(): 14 | global link_token 15 | link_token = request.json['data']['link_token'] 16 | return 'link token set' 17 | 18 | 19 | @app.route('/api/accounts', methods=['GET']) 20 | def get_accounts(): 21 | def transform_account(account): 22 | return { 23 | 'id': account.id_, 24 | 'name': account.name, 25 | 'holderName': account.holder_name, 26 | 'currency': account.currency, 27 | 'balance': { 28 | 'available': account.balance.available, 29 | 'current': account.balance.current, 30 | } 31 | } 32 | link = client.get_link(link_token) 33 | return jsonify([transform_account(account) for account in link]) 34 | 35 | 36 | @app.route('/api/accounts//movements', methods=['GET']) 37 | def get_account_movements(account_id): 38 | def transform_movement(movement): 39 | return { 40 | 'id': movement.id_, 41 | 'postDate': movement.post_date.isoformat(), 42 | 'amount': movement.amount, 43 | 'currency': movement.currency, 44 | 'description': movement.description, 45 | } 46 | current_month = date.today().replace(day=1) 47 | link = client.get_link(link_token) 48 | account = link.find(id_=account_id) 49 | movements = account.get_movements(since=current_month) 50 | return jsonify([transform_movement(movement) for movement in movements]) 51 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # X-COMMON-VARIABLES 2 | x-backend-environment: &backend-environment 3 | SECRET_KEY: ${SECRET_KEY} 4 | x-frontend-environment: &frontend-environment 5 | REACT_APP_PUBLIC_KEY: ${REACT_APP_PUBLIC_KEY} 6 | REACT_APP_WEBHOOK_URL: ${REACT_APP_WEBHOOK_URL} 7 | REACT_APP_BACKEND_HOST: ${REACT_APP_BACKEND_HOST} 8 | x-backend-networks: &backend-networks 9 | - backend_network 10 | x-backend-ports: &backend-ports 11 | - 5000:5000 12 | 13 | version: '3.8' 14 | services: 15 | # Backend Services 16 | node: 17 | container_name: fintoc_node 18 | build: 19 | dockerfile: Dockerfile 20 | context: ./node 21 | ports: *backend-ports 22 | environment: 23 | <<: *backend-environment 24 | networks: *backend-networks 25 | python: 26 | container_name: fintoc_python 27 | build: 28 | dockerfile: Dockerfile 29 | context: ./python 30 | ports: *backend-ports 31 | environment: 32 | <<: *backend-environment 33 | networks: *backend-networks 34 | ruby: 35 | container_name: fintoc_ruby 36 | build: 37 | dockerfile: Dockerfile 38 | context: ./ruby 39 | ports: *backend-ports 40 | environment: 41 | <<: *backend-environment 42 | networks: *backend-networks 43 | 44 | # Frontend services 45 | frontend: 46 | container_name: fintoc_frontend 47 | build: 48 | dockerfile: Dockerfile 49 | context: ./frontend 50 | ports: 51 | - 3000:3000 52 | environment: 53 | <<: *frontend-environment 54 | networks: 55 | - backend_network 56 | - frontend_network 57 | 58 | networks: 59 | frontend_network: 60 | name: frontend_network 61 | driver: bridge 62 | backend_network: 63 | name: backend_network 64 | driver: bridge 65 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Fintoc Quickstart 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/src/Components/ConnectLink/ConnectLink.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import useScript from 'react-script-hook'; 3 | 4 | const widgetOptions = { 5 | publicKey: process.env.REACT_APP_PUBLIC_KEY, 6 | holderType: 'individual', // business or individual 7 | product: 'movements', // movements or suscription 8 | webhookUrl: process.env.REACT_APP_WEBHOOK_URL, 9 | }; 10 | 11 | function ConnectLink(props) { 12 | const [isOpen, setIsOpen] = useState(true); 13 | const [loadingScript, errorScript] = useScript({ src: 'https://js.fintoc.com/v1/' }); 14 | 15 | const handleSuccess = (params) => { 16 | props.setLinkId(params.id); 17 | }; 18 | const openWidget = () => setIsOpen(true); 19 | const closeWidget = () => setIsOpen(false); 20 | useEffect(() => { 21 | if (!isOpen) return; 22 | if (loadingScript) return; 23 | if (errorScript || !window.Fintoc) return; 24 | const params = { 25 | ...widgetOptions, 26 | onSuccess: handleSuccess, 27 | onExit: closeWidget, 28 | }; 29 | const widget = window.Fintoc.create(params); 30 | widget.open(); 31 | }, [loadingScript, errorScript, isOpen]); 32 | 33 | return ( 34 |
35 |

36 | List@ para probar fintoc? 37 | Conecta tu cuenta para comenzar 38 |

39 |
40 |
41 | 47 |
48 |
49 |
50 | ); 51 | } 52 | 53 | export default ConnectLink; 54 | -------------------------------------------------------------------------------- /frontend/src/Components/MovementList/MovementList.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { formatAmount, formatDate } from '../../helpers/utils'; 3 | 4 | function MovementList(props) { 5 | const [movements, setMovements] = useState([]); 6 | const [isLoading, setIsLoading] = useState(false); 7 | const [error, setError] = useState(false); 8 | 9 | useEffect(async () => { 10 | setIsLoading(true); 11 | const response = await fetch(`/api/accounts/${props.accountId}/movements?linkId=${props.linkId}`); 12 | const data = await response.json(); 13 | if (response.ok) { 14 | setMovements(data); 15 | setError(false); 16 | } else { 17 | setError(data.message); 18 | } 19 | setIsLoading(false); 20 | }, []); 21 | 22 | const handleBack = () => { 23 | props.setAccountId(''); 24 | }; 25 | 26 | return ( 27 |
28 | 29 |

Movimientos de la cuenta

30 | {error && ( 31 | 35 | )} 36 | {!error 37 | && ( 38 |
39 | 40 | 41 | 42 | 46 | 50 | 54 | 55 | 56 | 57 | {!isLoading && movements.map((movement) => ( 58 | 59 | 60 | 61 | 62 | 63 | ))} 64 | {isLoading && ( 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | )} 73 | 74 |
Fecha contableMontoDescripción
{formatDate(movement.postDate)}{movement.currency} {formatAmount(movement.amount)}{movement.description}
75 |
76 | )} 77 |
78 | ); 79 | } 80 | 81 | export default MovementList; 82 | -------------------------------------------------------------------------------- /frontend/src/Components/AccountList/AccountList.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { formatAmount } from '../../helpers/utils'; 3 | 4 | function AccountList(props) { 5 | const [accounts, setAccounts] = useState([]); 6 | const [isLoading, setIsLoading] = useState(false); 7 | const [error, setError] = useState(false); 8 | 9 | useEffect(async () => { 10 | setIsLoading(true); 11 | const response = await fetch(`/api/accounts?linkId=${props.linkId}`); 12 | const data = await response.json(); 13 | if (response.ok) { 14 | setAccounts(data); 15 | setError(false); 16 | } else { 17 | setError(data.message); 18 | } 19 | setIsLoading(false); 20 | }, []); 21 | 22 | const handleButtonClick = (event) => { 23 | props.setAccountId(event.target.getAttribute('account-id')); 24 | }; 25 | 26 | const handleReconnect = () => { 27 | props.setLinkId(''); 28 | }; 29 | 30 | return ( 31 |
32 |

Balance de tus cuentas

33 | {error && ( 34 | 39 | )} 40 | {!error 41 | && ( 42 |
43 | 44 | 45 | 46 | 50 | 54 | 58 | 62 | 65 | 66 | 67 | 68 | {!isLoading && accounts.map((account) => ( 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | ))} 81 | {isLoading && ( 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | )} 90 | 91 |
Tipo de cuentaNombreSaldo disponibleSaldo contable 63 | Ver movimientos 64 |
{account.name}{account.holderName}{account.currency} {formatAmount(account.balance.available)}{account.currency} {formatAmount(account.balance.current)} 75 | 78 |
92 |
93 | )} 94 |
95 | ); 96 | } 97 | 98 | export default AccountList; 99 | -------------------------------------------------------------------------------- /frontend/src/imagotipo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Fintoc Quickstart 3 | **Fintoc Quickstart** is the repository that allows you to test Fintoc tools locally! 4 | The purpose of the repository is that you know how to integrate the Fintoc Widget and the SDKs that we offer for the different supported languages. 5 | **Fintoc Quickstart** is composed mainly by what we call a frontend application, which is an application written in [React](https://reactjs.org/) and that integrates the Fintoc [Widget](https://docs.fintoc.com/docs/usando-el-widget#flujo-del-widget), and also by backend applications which represent the backend and that use the SDKs that Fintoc provides. 6 | Currently the languages supported by the Fintoc SDKs are: [**Node.js**](https://github.com/fintoc-com/fintoc-node), [**Ruby**](https://github.com/fintoc-com/fintoc-ruby) and [**Python**](https://github.com/fintoc-com/fintoc-python), but if you are looking for another language do not worry, we are working to support others! 7 | 8 |

9 | 10 |

11 | 12 | # Table of content 13 | * [Fintoc Quickstart](#fintoc-quickstart) 14 | * [Table of content](#table-of-content) 15 | * [1. Clone the repository](#1-clone-the-repository) 16 | * [2. Get your keys and setup](#2-get-your-keys-and-setup) 17 | * [3. Run Fintoc Quickstart](#3-run-fintoc-quickstart) 18 | * [3.1. Expose backend to internet (ngrok)](#31-expose-backend-to-internet-ngrok) 19 | * [3.2. Run](#32-run) 20 | * [3.2.1. Prerequisites](#321-prerequisites) 21 | * [3.2.2. Commands](#322-commands) 22 | * [Frontend](#frontend) 23 | * [Node](#node) 24 | * [Python](#python) 25 | * [Ruby](#ruby) 26 | * [3.3. Run + Docker](#33-run--docker) 27 | * [3.3.1. Prerequisites](#331-prerequisites) 28 | * [3.3.2. Commands](#332-commands) 29 | * [help](#help) 30 | * [start](#start) 31 | * [build](#build) 32 | * [destroy](#destroy) 33 | * [stop](#stop) 34 | * [restart](#restart) 35 | * [logs](#logs) 36 | 37 | # 1. Clone the repository 38 | Clone the repository using your prefered terminal using any of the following commands: 39 | 40 | | Protocol | Command | 41 | |------------|----------------------------------------------------------------| 42 | | HTTPS | ```$ git clone https://github.com/fintoc-com/quickstart.git``` | 43 | | SSH | ```$ git clone git@github.com:fintoc-com/quickstart.git``` | 44 | | GitHub CLI | ```$ gh repo clone fintoc-com/quickstart``` | 45 | 46 | And go to the **Fintoc Quickstart** directory: 47 | 48 | ```$ cd quickstart``` 49 | # 2. Get your keys and setup 50 | To obtain the keys it will be necessary that you have a Fintoc account created, and that you have a bank account associated with it. 51 | After that you can get the Public and Secret keys by accessing the API Keys page ([Dashboard](https://app.fintoc.com/api-keys) > API Keys). More information on the process can be found at: [docs.fintoc.com/docs/quickstart](https://docs.fintoc.com/docs/quickstart). 52 | 53 | Your keys will look like this: 54 | 55 | | Key | Value | 56 | |-----------------|------------------------------------| 57 | | YOUR_PUBLIC_KEY | pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXX | 58 | | YOUR_SECRET_KEY | sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXX | 59 | 60 | > **Note:** If you use your **TEST KEYS**, then your keys will have the word ```_test_``` instead of ```_live_```. 61 | 62 | Create an ```.env``` file (where the environment variables will be loaded) by copying our example ```.env.example``` file: 63 | 64 | ```shell 65 | $ cp .env.example .env 66 | ``` 67 | And add the **Secret** and **Public** keys to the environment variables ```SECRET_KEY``` and ```REACT_APP_PUBLIC_KEY``` respectively, as shown below. 68 | 69 | ```.dotenv 70 | SECRET_KEY= 71 | REACT_APP_PUBLIC_KEY= 72 | ... 73 | ``` 74 | 75 | # 3. Run Fintoc Quickstart 76 | **Fintoc Quickstart** allows you to run the services in two ways, directly or with [Docker](https://www.docker.com/). 77 | We recommend running it with **Docker** because it is very fast and simple! 78 | But if you want to run the applications directly, no problem, we provide you with the instructions to do so! 79 | 80 | ## 3.1. Expose backend to internet (ngrok) 81 | Widget integration requires exposing a backend endpoint (Webhook) to the internet, so that Fintoc can send a *link_token* to the backend application and with this access the necessary information. 82 | > **Note:** *link_token* is the token that allows access to the information belonging to the user who started the session. 83 | > More information about the Widget can be found at: [docs.fintoc.com/docs/usando-el-widget#flujo-del-widget](https://docs.fintoc.com/docs/usando-el-widget#flujo-del-widget). 84 | 85 | For this we will use [ngrok](https://ngrok.com/), which is a cross-platform program that enables developers to expose local servers behind NATs and firewalls to the public internet over secure tunnels with minimal effort. 86 | In this way, using **ngrok** we can expose the backend (and the webhook) so that Fintoc can communicate with it. 87 | 88 | To do this, download ngrok ([ngrok.com/download](https://ngrok.com/download)) for the operating system you use and execute the following command in a new session of the terminal: 89 | 90 | ```shell 91 | $ ./ngrok http 5000 92 | ``` 93 | > **Note**: **Fintoc Quickstart** backend applications run (and expose) on port **5000** 94 | 95 | This will show a message in which we are interested in the line that says ```Forwarding https...```, as shown below: 96 | ```shell 97 | ... 98 | Forwarding https://xxxxxxxxxxxxxx.ngrok.io -> http://localhost:5000 99 | ... 100 | ``` 101 | So we need to copy the public url (```https://xxxxxxxxxxxxxx.ngrok.io```), and paste it in the ```.env``` file to the ```REACT_APP_WEBHOOK_URL``` environment variable. 102 | At the end the ```.env``` file should look like this: 103 | ```.dotenv 104 | SECRET_KEY= 105 | REACT_APP_PUBLIC_KEY= 106 | REACT_APP_WEBHOOK_URL=/api/link_token 107 | ``` 108 | > **Important:** It is important that the public url (```NGROK_PUBLIC_WEBHOOK_URL```) is suffixed with the path ```/api/link_token```, since that is the webhook endpoint that the backend applications of this project have. 109 | ## 3.2. Run 110 | ### 3.2.1. Prerequisites 111 | To run the applications directly it is necessary to have the language installed on your machine. 112 | * **node (frontend & node):** 10 or higher 113 | * **python:** 3.6.1 or higher 114 | * **ruby:** 2.7 or higher 115 | 116 | ### 3.2.2. Commands 117 | * #### Frontend 118 | ```shell 119 | $ cd frontend 120 | $ npm install 121 | $ npm start 122 | ``` 123 | * #### Node 124 | ```shell 125 | $ cd node 126 | $ npm install 127 | $ npm start 128 | ``` 129 | * #### Python 130 | ```shell 131 | $ cd python 132 | 133 | # optional: create a virtual environment to keep your dependencies clean 134 | $ virtualenv venv 135 | $ source ./venv/bin/activate 136 | 137 | $ pip install -r requirements.txt 138 | $ FLASK_APP=app.py flask run 139 | ``` 140 | 141 | * #### Ruby 142 | ```shell 143 | $ cd ruby 144 | $ bundle install 145 | $ ruby app.rb 146 | ``` 147 | 148 | ## 3.3. Run + Docker 149 | ### 3.3.1. Prerequisites 150 | To run **Fintoc Quickstart** with **Docker** you need to have: 151 | * [**Docker**](https://docs.docker.com/get-docker/) 152 | * [**GNU make**](https://www.gnu.org/software/make/manual/make.html) 153 | 154 | ### 3.3.2. Commands 155 | * #### help 156 | Show commands and their options. 157 | ```shell 158 | $ make help 159 | ``` 160 | 161 | * #### start 162 | Start the containers (and builds them if they don't exist). 163 | ```shell 164 | $ make start language=[language] 165 | ``` 166 | ```[language]``` can be one of the following options: ```node```, ```python``` or ```ruby```. 167 | 168 | * #### build 169 | Build the Docker images. 170 | ```shell 171 | $ make build 172 | ``` 173 | 174 | * #### destroy 175 | Destroy the containers. 176 | ```shell 177 | $ make destroy 178 | ``` 179 | 180 | * #### stop 181 | Stop the containers. 182 | ```shell 183 | $ make stop 184 | ``` 185 | 186 | * #### restart 187 | Restart the containers. 188 | ```shell 189 | $ make restart 190 | ``` 191 | 192 | * #### logs 193 | Show containers logs. 194 | ```shell 195 | $ make logs 196 | ``` 197 | --------------------------------------------------------------------------------