Wilfredo Meneses
Profesor universitario
http://slides.com/meneboni/aplicaciones-web
Unidad I: Programación para la Web
Unidad II: Backend
Unidad III: Frontend
Unidad IV: Deployment y Administración
1. Instalador de node.js
node -v2. Prettier - Formatea el código
mkdir server
cd server
npm init
npm install --save expressconst express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send({ hola: 'Mundo' });
});
app.listen(5000);
// Desde la consola ejecutamos el comando: node index.js
// En el navegador escribimos http://localhost:5000/En muchos entornos (por ejemplo, Heroku), y como convención, configuran la variable de entorno PORT para decirle a su servidor web en qué puerto escuchar.
Entonces process.env.PORT || 5000 significa: lo que sea que esté en la variable de entorno PORT, o 5000 si no hay nada allí.
const PORT = process.env.PORT || 5000;
app.listen(PORT);modificamos index.js
"main": "index.js",
"engines": {
"node": "12.14.1",
"npm": "6.13.4"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},modificamos package.json
Agregar "engines" entre "main" y "script" para especificar el entorno que tenemos en local y evitar errores de compatibilidad en la versión en producción
"scripts": {
"start": "node index.js"
},modificamos package.json
Dentro de "scripts" borramos "test" y agregamos "start" con el comando que hemos especificado para ejecutar index.js en local
node_modulesCuando instalamos dependencias como express, de forma común no se controla versión de los mismos, tampoco deben desplegarse en heroku, porque haremos que heroku haga las instalaciones por si mismo. Para eso es .gitignore
www.heroku.com
git init
git add .
git commit -m "Commit inicial"//Desde la línea de comandos
heroku -v
//Para hacer login solicitará el email y contraseña de nuestra cuenta heroku
heroku login
// Creamos la App. Heroku le asigna un nombre
// y provee la ruta git para el diployment
heroku create//Desde la línea de comandos
heroku -v
//Para hacer login solicitará el email y contraseña de nuestra cuenta heroku
heroku login
// Creamos la App. Heroku le asigna un nombre
// y provee la ruta git para el diployment
heroku create
// Ejemplo de proyecto creado en Heroku
// https://fathomless-waters-86018.herokuapp.com/ | https://git.heroku.com/fathomless-waters-86018.git
// Agregar el repositorio remoto
git remote add heroku https://git.heroku.com/fathomless-waters-86018.git
// Pasar la información del repositorio local al remoto
git push heroku master
// Probamos la App
heroku open
// En caso de error
heroku logsOpen Authorization es un estándar abierto que permite flujos simples de autorización para sitios web o aplicaciones informáticas. Se trata de un protocolo propuesto por Blaine Cook y Chris Messina, que permite autorización segura de una API de modo estándar y simple para aplicaciones de escritorio, móviles y web.
Tomado de: https://es.wikipedia.org/wiki/OAuth
npm install --save passport passport-google-oauth20 const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const app = express();
passport.use(new GoogleStrategy());
const PORT = process.env.PORT || 5000;
app.listen(PORT);
index.js
En la terminal
Hacer que Google conozca nuestra aplicación
Hacer que Google conozca nuestra aplicación
1. Crear el arhivo keys.js
module.exports = {
googleClientID: '439988331208-j7m4lnq3m4sr14soce1ro8c7l41vvp6e.apps.googleusercontent.com',
googleClientSecret: 'PUkKItVVoJ3nRQKqoTybgcWy'
}2. Exportar claves para que puedan ser accedidas desde otros archivos
3. Ignorar las claves para que no estén disponibles en los diployment. Esto se hace desde el .gitignore
node_modules
keys.jsconst express = require("express");
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require("./config/keys");
const app = express();
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
accessToken => {
console.log(accessToken);
}
)
);
const PORT = process.env.PORT || 5000;
app.listen(PORT);
app.get(
"/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"]
})
);Nota: este cambio puede tomar en algunos casos hasta 5 minutos para que google agregue la redirección.
app.get('/auth/google/callback', passport.authenticate('google'));Ver código generado por el accessToken
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
(accessToken, refreshToken, profile, done) => {
console.log('access token: ', accessToken);
console.log('refresh token: ', refreshToken);
console.log('profile: ', profile);
}
)
);npm install --save nodemonEn la terminal
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},En package.json
npm run devEn la terminal
Nodemon es una utilidad que monitorea los cambios en el código fuente que se esta desarrollando y automáticamente reinicia el servidor.
const express = require("express");
require('./services/passport');
// const authRoutes = require('./routes/authRoutes');
const app = express();
require('./routes/authRoutes')(app);
const PORT = process.env.PORT || 5000;
app.listen(PORT);
const passport = require('passport');
module.exports = app => {
app.get(
"/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"]
})
);
app.get('/auth/google/callback', passport.authenticate('google'));
}index.js
authRoutes.js
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require("../config/keys");
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
(accessToken, refreshToken, profile, done) => {
console.log('access token: ', accessToken);
console.log('refresh token: ', refreshToken);
console.log('profile: ', profile);
}
)
);passport.js
(records)
Código fuente, ejemplo conexión a BD: https://github.com/Saturday-Developer/MongoDB-connection-using-Node-JS
1
2
3
4
5
6
7
8
9
10
npm install --save mongoose1. En la terminal
const express = require("express");
const mongoose = require('mongoose');
require('./services/passport');
const keys = require("./config/keys");
mongoose.connect(
keys.mongoURI,
{ useNewUrlParser: true,
useUnifiedTopology: true }
).then(console.log('Conexión establecida'));2. En la keys.js
module.exports = {
googleClientID: '439988331208-j7m4lnq3m4sr14soce1ro8c7l41vvp6e.apps.googleusercontent.com',
googleClientSecret: 'PUkKItVVoJ3nRQKqoTybgcWy',
mongoURI: 'mongodb://wilfredo:Password1.@ds061711.mlab.com:61711/mb-feedback-dev'
}3. En la index.js
npm run dev4. En la terminal
1. Crear una nueva carpeta
2. En User.js
const mongoose = require('mongoose');
// const Schema = mongoose.Schema;
const { Schema } = mongoose;
const userSchema = new Schema({
googleId: String
});
mongoose.model('users', userSchema);3. Aplicar el require en index.js
const express = require("express");
const mongoose = require('mongoose');
const keys = require("./config/keys");
require('./models/User');
require('./services/passport');
//...const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const mongoose = require('mongoose');
const keys = require("../config/keys");
const User = mongoose.model('users');
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
(accessToken, refreshToken, profile, done) => {
new User({ googleId: profile.id }).save();
}
)
);// ... el código anterior queda igual
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
(accessToken, refreshToken, profile, done) => {
// Retorna una promesa
User.findOne({ googleId: profile.id })
.then( (existingUser) => {
if(existingUser) {
// Tenemos un registro con el Id de perfil
} else {
// No tenemos un registro del Id de Perfil,
// hacemos un nuevo registro
new User({ googleId: profile.id }).save();
}
}
);
}
)
);// ... el código anterior queda igual
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
(accessToken, refreshToken, profile, done) => {
// Retorna una promesa
User.findOne({ googleId: profile.id })
.then( (existingUser) => {
if(existingUser) {
// Tenemos un registro con el Id de perfil
// El primer parámtro indica que no hay error
// El segundo parámetro indica a passport el
// usuario que hemos encontrado
done(null, existingUser);
} else {
// No tenemos un registro del Id de Perfil, hacemos un nuevo registro
new User({ googleId: profile.id })
.save()
.then(user => DelayNode(null, user));
}
}
);
}
)
);En passport.js
// ... el código anterio queda igual
const User = mongoose.model('users');
passport.serializeUser((user, done)=> {
done(null, user.id);
});
// ... el resto de código queda igual¿Por qué el ID interno y no el del perfil?
passport.serializeUser () configura el id como cookie en el navegador del usuario y passport.deserializeUser () obtiene la identificación de la cookie.
En passport.js
// ... el código anterio queda igual
passport.serializeUser((user, done)=> {
done(null, user.id);
});
// El primer pqarámetro del arrow function es el token (id)
// que deseamos buscar en la cookie
passport.deserializeUser( (id, done) => {
User.findById(id)
.then(user => {
done(null, user);
});
});
// ... el resto de código queda igual1. Desde la terminal
npm install --save cookie-session2. En index.js
// ... el código anterior queda igual
const mongoose = require('mongoose');
const cookieSession = require('cookie-session');
const passport = require('passport');
// ... el código posterior queda igual3. En keys.js, agregar coockieKey
module.exports = {
//... lo anterior queda igual
cookieKey: 'qiwpsbjtarsporle'
}// ... el resto de código queda igual
const app = express();
app.use(
cookieSession({
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.cookieKey]
})
);
app.use(passport.initialize());
app.use(passport.session());4. En el index.js
app.get('/api/current_user', (req, res) => {
res.send(req.user);
});Agregar esta ruta en authRoute
app.get('/api/logout', (req, res) => {
req.logout();
res.send(req.user);
});Middleware en Express JS. Un middleware es una función que se puede ejecutar antes o después del manejo de una ruta.
cookie-session
express-session
Debe evitarse que si alguien accede a nuestra computadora pueda tomar las claves (keys) y afectar nuestro entorno de producción. Por eso en producción debemos tener otras keys por seguridad.
En la versión en producción deben usarse credenciales seguras. En nuestro caso es básica por fines de simplicidad.
1
2
3
Desde la terminal ejecutamos el siguiente comando para obtener la ruta de la aplicación alojada en Heroku
heroku openmodule.exports = {
googleClientID: '439988331208-j7m4lnq3m4sr14soce1ro8c7l41vvp6e.apps.googleusercontent.com',
googleClientSecret: 'PUkKItVVoJ3nRQKqoTybgcWy',
mongoURI: 'mongodb://wilfredo:Password1.@ds061711.mlab.com:61711/mb-feedback-dev',
cookieKey: 'qiwpsbjtarsporle'
}En dev.js
module.exports = {
googleClientID: process.env.GOOGLE_CLIENT_ID,
googleClientSecret: process.env.GOOGLE_CLIENT_SECRET,
mongoURI: process.env.MONGO_URI,
cookieKey: process.env.COOKIE_KEY
}En prod.js
node_modules
dev.jsEn .gitignore
if(process.env.NODE_ENV === 'production') {
module.exports = require('./prod');
} else {
module.exports = require('./dev');
}En keys.js
1
2
3
En la terminal
git status
git add .
git commit -m "Flujo Auth finalizado"
git push heroku master
heroku opennew GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback",
proxy: true
},
// el resto queda igualgit status
git add .
git commit -m "Ajuste al Google Strategy"
git push heroku master
heroku open(El lado del cliente)
npm install -g create-react-appSe recomienda que la consola tenga permisos de administrador
npx create-react-app my-app
// Client tiene su propio servidor
cd client
npm startSe puede trabajar juntos, pero de forma separada create-react-app ahorra mucho tiempo con la configuración de dependencias.
1. Se puede trabajar con dos consolas separadas
Consola 1 - en la carpeta de "client"
Consola 2 - desde la carpeta "server"
npm startnpm run devnpm install --save concurrently2. Usando concurrently
"scripts": {
"start": "node index.js",
"server": "nodemon index.js",
"client": "npm run start --prefix client",
"dev": "concurrently \"npm run server\" \"npm run client\""
},En package.json
Desde server
npm run devTerminal
Si intentamos crear un enlace en el front-end que nos lleve a /auth/google tendremos problemas porque la ruta es manejada desde el backend, esta ruta puede cambiar según se trabaje en dev o prod
Solución: desde el client
cd client
npm install http-proxy-middleware --saveAhora cree un archivo de configuración para su proxy. Debe nombrarlo setupProxy.js en su carpeta src en el lado del cliente y escriba el siguiente código:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/auth/google',
createProxyMiddleware({
target: 'http://localhost:5000'
})
);
};
cd client
npm run buildNota: Hay que agregar el callback
function fetchAlbums() {
fetch('http://rallycoding.herokuapp.com/api/music_albums')
.then(res => res.json())
.then(json => console.log(json));
}
fetchAlbums();async function fetchAlbums() {
const res = await fetch('http://rallycoding.herokuapp.com/api/music_albums')
const json = await res.json();
console.log(json);
}
fetchAlbums();Esta forma es más sencilla y es con la que trabajaremos de ahora en adelante:
const fetchAlbums = async () => {
const res = await fetch('http://rallycoding.herokuapp.com/api/music_albums')
const json = await res.json();
console.log(json);
}
fetchAlbums();Con arrow function
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
// Tenemos un registro con el Id de perfil
return done(null, existingUser);
}
// No tenemos un registro del Id de Perfil, hacemos un nuevo registro
const user = await new User({ googleId: profile.id }).save();
done(null, user);
}En passport.js
Nota: se requiere una versión de node mínima 8.1.1
Dejamos el directorio src de la siguiente forma (todo va desde cero):
cd client
npm install --save redux react-redux react-router-domEn index.js
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(); 2. En index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
ReactDOM.render(<App />, document.querySelector("#root"));Para exportar componentes los nombres de los archivos inician con mayúsculas con convensión
import React from 'react';
const App = () => {
return (
<div>
Hola
</div>
)
}
export default App;1. En App.js
3. En la Terminal
cd ..En index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import App from "./components/App";
const store = createStore(() => [], {}, applyMiddleware());
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.querySelector("#root")
);
2. En authReducer.js
export default function(state = {}, action) {
switch(action.type) {
default:
return state;
}
}1. Crear la siguiente estructura
3. En index.js del directorio reducers
import { combineReducers } from 'redux';
import authReducer from './authReducer';
export default combineReducers({
auth: authReducer
});4. Modificar en index.js de src
import App from "./components/App";
import reducers from './reducers';
const store = createStore(reducers, {}, applyMiddleware());import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;
const App = () => {
return (
<div>
<BrowserRouter>
<div>
<Route path="/" component={Landing} />
</div>
</BrowserRouter>
</div>
)
}
export default App;En components/App.js
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;
const App = () => {
return (
<div>
<BrowserRouter>
<div>
<Route exact path="/" component={Landing} />
<Route path="/surveys" component={Dashboard} />
</div>
</BrowserRouter>
</div>
)
}
export default App;En components/App.js
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;
const App = () => {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={Landing} />
<Route exact path="/surveys" component={Dashboard} />
<Route path="/surveys/new" component={SurveyNew} />
</div>
</BrowserRouter>
</div>
)
}
export default App;En components/App.js
import React, { Component } from 'react';
class Header extends Component {
render(){
return (
<div>
Header
</div>
);
}
}
export default Header;2. Header.js
1. En components/App.js
3. En App.js
// Reemplazar
const Header = () => <h2>Header</h2>;
// por
import Header from './Header';Para la apariencia vamos a utilizar un framework de propósito general que puede utilizar con cualquier librería o framework JavaScript
cd client
npm install --save materialize-cssDesde la terminal
En la primera línea de index.js dentro de src
import 'materialize-css/dist/css/materialize.min.css';import React, { Component } from 'react';
class Header extends Component {
render(){
return (
<nav>
<div className="nav-wrapper">
<a href="#" className="left brand-logo">mb-feedback</a>
<ul id="nav-mobile" className="right hide-on-med-and-down">
<li><a href="#">Login con Google</a></li>
</ul>
</div>
</nav>
);
}
}
export default Header;return (
<div className="container">
<BrowserRouter>En App.js (Poner la clase container)
cd client
npm install --save axios redux-thunkEn la terminal
// ....
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import App from "./components/App";
import reducers from './reducers';
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
// ....En index.js de src
app.use(
'/api/*',
createProxyMiddleware({
target: 'http://localhost:5000'
})
); 1. En setupProxy.js agregar
2. Crear la siguiente estructura
3. En types.js
export const FETCH_USER = 'fetch_user';// Para hacer peticiones ajax
import axios from 'axios';
import { FETCH_USER } from './types';
export const fetchUser = () => {
axios.get('/api/current_user');
}4. En index.js
// Para hacer peticiones ajax
import axios from "axios";
import { FETCH_USER } from "./types";
export const fetchUser = () => {
return function(dispatch) {
axios.get("/api/current_user").then(res =>
dispatch({
type: FETCH_USER,
payload: res
})
);
};
};
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Header from './Header';
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;
class App extends Component {
componentDidMount() {
}
render() {
return (
<div className="container">
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={Landing} />
<Route exact path="/surveys" component={Dashboard} />
<Route path="/surveys/new" component={SurveyNew} />
</div>
</BrowserRouter>
</div>
)
};
}
export default App;En App.js
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
// Agregarmos estas importaciones para conectar la App con redux
import { connect } from 'react-redux';
import * as actions from '../actions';
// ...
componentDidMount() {
// Llamada al action
this.props.fetchUser();
}
// ...
// Pasamos los argumentos a App
export default connect(null, actions) (App);En authReducer.js
export default function(state = {}, action) {
console.log(action);
switch(action.type) {
default:
return state;
}
}En actions/index.js
// Para hacer peticiones ajax
import axios from "axios";
import { FETCH_USER } from "./types";
export const fetchUser = () => async dispatch => {
const res = await axios.get("/api/current_user");
dispatch({ type: FETCH_USER, payload: res.data });
};
import { FETCH_USER } from '../actions/types';
export default function(state = null, action) {
switch(action.type) {
case FETCH_USER:
return action.payload || false;
default:
return state;
}
}En authReducer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Header extends Component {
renderContent() {
switch(this.props.auth) {
case null:
return 'Todavía decidiendo';
case false:
return 'Sessión cerrada';
default:
return 'Sesión iniciada';
}
}
render(){
// console.log(this.props);
return (
<nav>
<div className="nav-wrapper">
<a href="#" className="brand-logo">mb-feedback</a>
<ul id="nav-mobile" className="right hide-on-med-and-down">
{ this.renderContent() }
</ul>
</div>
</nav>
);
}
}
// function mapStateToProps(state) {
// return { auth: state.auth };
// }
function mapStateToProps({ auth }) {
return { auth };
}
export default connect(mapStateToProps)(Header);En Header.js
// ...
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return <li><a href="/auth/google">Acceso con Google</a></li>;
default:
return <li><a href="">Salir</a></li>;
}
}
// ...En Header.js
// ...
app.get(
'/auth/google/callback',
passport.authenticate('google'),
(req, res) => {
res.redirect('/surveys');
}
);
// ...Del lado del servidor authRoutes.js, modificar
// ...
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return <li><a href="/auth/google">Acceso con Google</a></li>;
default:
return <li><a href="/api/logout">Salir</a></li>;
}
}
// ...Del lado del cliente, en Header.js
Del lado del servidor, en authRoutes.js
// ...
app.get('/api/logout', (req, res) => {
req.logout();
res.redirect('/');
});
// ...import React from 'react';
const Landing = () => {
return(
<div style={{textAlign: 'center'}}>
<h1>mb-feedback</h1>
Obtener feedback de sus usuarios
</div>
);
};
export default Landing;Del lado del cliente, en Landing.js
En App.js
// Reemplazar
const Landing = () => <h2>Landing</h2>;
// Por
import Landing from './Landing';
// Poner esto anter de las otras declaraciones para evitar errorEn App.js
En Header.js
// ...
import { Link } from "react-router-dom";
// ...
render() {
return (
<nav>
<div className="nav-wrapper">
<Link
to={this.props.auth ? '/surveys' : '/'}
className="brand-logo"
>
mb-feedback
</Link>
<ul id="nav-mobile" className="right hide-on-med-and-down">
{this.renderContent()}
</ul>
</div>
</nav>
);
}
Somos malos en seguridad
La facturación es difícil
Somos malos en seguridad
La facturación es difícil
// Desde la terminal
cd client
npm install --save react-stripe-checkoutInstalar react-stripe-checkout
module.exports = {
// ...
stripePublishableKey: 'pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi',
stripeSecretKey: 'sk_test_NFUt0kfcFB0f8IMD39dG79FB00mqd8iN89'
}En config/dev.js
module.exports = {
// ...
stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
stripeSecretKey: process.env.STRIPE_SECRET_KEY
}En config/prod.js
En heroku.com, agregar las variables al proyecto
REACT_APP_STRIPE_KEY=pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi.env.development
REACT_APP_STRIPE_KEY=pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi.env.production
// ...
console.log('STRIPE KEY ES', process.env.REACT_APP_STRIPE_KEY);
console.log('Eviroment es', process.env.NODE_ENV);Para probar en index.js del client
Payments.js
import React, { Component } from 'react';
import StripeCheckout from 'react-stripe-checkout';
class Payments extends Component {
render() {
debugger;
return (
<StripeCheckout
amount={500}
token={token => console.log(token)}
stripeKey={process.env.REACT_APP_STRIPE_KEY}
/>
);
}
}
export default Payments;En Header.js
// ...
import Payments from './Payments';// ...
class Header extends Component {
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return <li><a href="/auth/google">Acceso con Google</a></li>;
default:
return [
<li key="1"><Payments /></li>,
<li key="2"><a href="/api/logout">Salir</a></li>
];
}
}
// ...
En Header.js
Pruebe a hacer un pago y vea en la consola el token y los resultados devueltos por Stripe
import React, { Component } from 'react';
import StripeCheckout from 'react-stripe-checkout';
class Payments extends Component {
render() {
return (
<StripeCheckout
name="mb-feeback"
description="$5 para 5 créditos emails"
amount={500}
token={token => console.log(token)}
stripeKey={process.env.REACT_APP_STRIPE_KEY}
>
<button className="btn">
Agregar Créditos
</button>
</StripeCheckout>
);
}
}
export default Payments;En Payments.js
En actions/index.js
// ...
export const handleToken = (token) => async dispatch => {
const res = await axios.post("/api/stripe", token);
dispatch({ type: FETCH_USER, payload: res.data });
};En Payments.js
// ...
import { connect } from 'react-redux';
import * as actions from '../actions';
class Payments extends Component {
render() {
return (
<StripeCheckout
name="mb-feeback"
description="$5 para 5 créditos emails"
amount={500}
token={token => this.props.handleToken(token)}
stripeKey={process.env.REACT_APP_STRIPE_KEY}
>
<button className="btn">
Agregar Créditos
</button>
</StripeCheckout>
);
}
}
export default connect(null, actions)(Payments);En el server index.js
// ...
require('./routes/authRoutes')(app);
require('./routes/billingRoutes')(app);
// ...En billingRoutes.js
module.exports = app => {
app.post('/api/stripe', (req, res) => {
});
}Vamos a instalar desde el servidor una librería para Stripe que nos permite manejar los tokens que recibimos desde el front end y cargar ese monto en los créditos para emails: https://www.npmjs.com/package/stripe
// Desde el servidor
npm install --save stripe
npm install --save body-parser// ...
const passport = require('passport');
const bodyParser = require('body-parser');
// ...
app.use(bodyParser.json());
app.use(
// ...En index.js (server)
const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);
module.exports = app => {
app.post('/api/stripe', (req, res) => {
console.log(req.body);
});
}En billingRoutes.js (server)
const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);
module.exports = app => {
app.post('/api/stripe', (req, res) => {
stripe.charges.create({
amount: 500,
currency: 'usd',
description: '$5 para 5 créditos emails',
source: req.body.id
});
});
}En billingRoutes.js
const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);
module.exports = app => {
app.post('/api/stripe', async (req, res) => {
const charge = await stripe.charges.create({
amount: 500,
currency: 'usd',
description: '$5 para 5 créditos emails',
source: req.body.id
});
console.log(charge);
});
}En billingRoutes.js
// ...
const userSchema = new Schema({
googleId: String,
credits: { type: Number, default: 0 }
});
// ...En models/User.js
// ...
module.exports = app => {
app.post('/api/stripe', async (req, res) => {
const charge = await stripe.charges.create({
amount: 500,
currency: 'usd',
description: '$5 para 5 créditos emails',
source: req.body.id
});
req.user.credits += 5;
const user = await req.user.save();
res.send(user);
});
}En billingRoutes.js
// ...
module.exports = app => {
app.post('/api/stripe', async (req, res) => {
if(!req.user){
return res.status(401).send({ error: 'Debes hacer login' });
}
// ....En billingRoutes.js
Lo anterior funciona, pero si queremos restringir otras rutas para que requieran autenticación tendríamos que copiar el código anterior en cada ruta, por lo que la solución viene en la siguiente presentación.
Vamos a crear un nuevo middleware para asegurarnos de que el usuario ha iniciado sesión
module.exports = (req, res, next) => {
if(!req.user){
return res.status(401).send({ error: 'Debes hacer login' });
}
// Si no hay problemas puede continuar
next();
}En requireLogin.js
Vamos a crear un nuevo middleware para asegurarnos de que el usuario ha iniciado sesión
module.exports = (req, res, next) => {
if(!req.user){
return res.status(401).send({ error: 'Debes hacer login' });
}
// Si no hay problemas puede continuar
next();
}En requireLogin.js
const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);
const requireLogin = require('../middlewares/requireLogin');
module.exports = app => {
// Observe que no hay llamada, express lo hará cuando haya una
// solicitud/request, puede haber tantos middleware como sea
// necesario según la ruta
app.post('/api/stripe', requireLogin, async (req, res) => {
const charge = await stripe.charges.create({
amount: 500,
currency: 'usd',
description: '$5 para 5 créditos emails',
source: req.body.id
});
req.user.credits += 5;
const user = await req.user.save();
res.send(user);
});
}En billingRoutes.js
// ...
class Header extends Component {
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return <li><a href="/auth/google">Acceso con Google</a></li>;
default:
return [
<li key="1"><Payments /></li>,
<li key='3' style={{ margin: '0 10px'}}>
Credits: { this.props.auth.credits }
</li>,
<li key="2"><a href="/api/logout">Salir</a></li>
];
}
}
// ...En Header.js
// ...
class Header extends Component {
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return <li><a href="/auth/google">Acceso con Google</a></li>;
default:
return [
<li key="1"><Payments /></li>,
<li key='3' style={{ margin: '0 10px'}}>
Credits: { this.props.auth.credits }
</li>,
<li key="2"><a href="/api/logout">Salir</a></li>
];
}
}
// ...En Header.js
By Wilfredo Meneses