diff --git a/.gitignore b/.gitignore index 44ee4b2b58c7284cdf1e5ecd4f9d5e1fc0bc6787..840566445d1261e24377c4758fcb04b7f1634268 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ tmp/* config/steem.json config/steem-dev.json config/process.json -app/assets/static/*.js +app/assets/static/plugins.js diff --git a/app/assets/static/credit_card.js b/app/assets/static/credit_card.js new file mode 100644 index 0000000000000000000000000000000000000000..eb387b3874ea70e14d81221519d3effe22f02c08 --- /dev/null +++ b/app/assets/static/credit_card.js @@ -0,0 +1,36 @@ +$(function() { + const data = $('script[data-iso-key="_0"]'); + const json = JSON.parse(data.text()); + Stripe.setPublishableKey(json.stripe_key); +}); + +$('#credit-card-form').on('submit', function(e){ + e.preventDefault(); + event.preventDefault(); + $('form-errors').hide(); + Stripe.card.createToken({ + number: $('#card-number').val(), + cvc: $('#cvv').val(), + exp_month: $('#expiry-month').val(), + exp_year: $('#expiry-year').val() + }, stripeResponseHandler); + $('#submit-btn').prop("disabled", true); +}); + +function stripeResponseHandler(status, response) { + var $form = $('#credit-card-form'); + if (response.error) { + // Show the errors on the form + $('#form-errors').show(); + $('#form-errors').html(response.error.message); + $('#submit-btn').prop("disabled", false); + } else { + // response contains id and card, which contains additional card details + var token = response.id; + // Insert the token into the form so it gets submitted to the server + $form.append($('<input type="hidden" name="stripeToken" />').val(token)); + // and submit + console.log($form.get(0)); + $form.get(0).submit(); + } +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 83e444d3390bb94fe695d202c8c32901ece14bd3..c91d61a72c2c0a053376515b194dfdb96fe261ec 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -6056,6 +6056,18 @@ "from": "strip-json-comments@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" }, + "stripe": { + "version": "4.10.0", + "from": "stripe@latest", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-4.10.0.tgz", + "dependencies": { + "qs": { + "version": "2.4.2", + "from": "qs@>=2.4.2 <3.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz" + } + } + }, "style-loader": { "version": "0.13.1", "from": "style-loader@>=0.13.0 <0.14.0", diff --git a/package.json b/package.json index f4140417fd65a62ab0f07b9ba95d3e973c0d6cdf..3aa11464484f538a777236f4eb6addd21dae0aee 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "sequelize": "^3.21.0", "sequelize-cli": "^2.3.1", "speakingurl": "^9.0.0", + "stripe": "^4.10.0", "style-loader": "^0.13.0", "svg-inline-loader": "^0.4.0", "svg-inline-react": "^1.0.2", diff --git a/server/server.js b/server/server.js index ffd1873350918ab69f7f18924d2d0f0d017d7e3e..70ffe9a45c5988acf3278f327a077f6c096ccea4 100644 --- a/server/server.js +++ b/server/server.js @@ -11,6 +11,7 @@ import useOauthLogin from './api/oauth'; import useGeneralApi from './api/general'; import useAccountRecoveryApi from './api/account_recovery'; import useEnterAndConfirmEmailPages from './server_pages/enter_confirm_email'; +import useCreditCardForm from './server_pages/credit_card_form'; import isBot from 'koa-isbot'; import session from 'koa-session'; import csrf from 'koa-csrf'; @@ -79,6 +80,7 @@ app.use(mount('/robots.txt', function* () { useRedirects(app); useEnterAndConfirmEmailPages(app); +useCreditCardForm(app); if (env === 'production') { app.use(helmet.contentSecurityPolicy(config.helmet)); diff --git a/server/server_pages/credit_card_form.jsx b/server/server_pages/credit_card_form.jsx new file mode 100644 index 0000000000000000000000000000000000000000..55309124033b9622eefa3ffc82607d3434e2980c --- /dev/null +++ b/server/server_pages/credit_card_form.jsx @@ -0,0 +1,178 @@ +import koa_router from 'koa-router'; +import koa_body from 'koa-body'; +import request from 'co-request'; +import Stripe from 'stripe'; +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import models from 'db/models'; +import {esc, escAttrs} from 'db/models'; +import ServerHTML from '../server-html'; +import Icon from 'app/components/elements/Icon.jsx'; +import sendEmail from '../sendEmail'; +import {checkCSRF} from '../utils'; +import config from '../../config'; +import Iso from 'iso'; + +const stripe = Stripe(config.stripe.secret_key); + +let assets; +if (process.env.NODE_ENV === 'production') { + assets = Object.assign({}, require('tmp/webpack-stats-prod.json'), {script: []}); +} else { + assets = Object.assign({}, require('tmp/webpack-stats-dev.json'), {script: []}); +} +assets.script.push('https://js.stripe.com/v2/'); +assets.script.push('https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js'); +assets.script.push('/static/credit_card.js'); + +const header = <header className="Header"> + <div className="Header__top header"> + <div className="expanded row"> + <div className="columns"> + <ul className="menu"> + <li className="Header__top-logo"> + <a href="/"><Icon name="steem" size="2x" /></a> + </li> + <li className="Header__top-steemit show-for-medium"><a href="/">steemit<span className="beta">beta</span></a></li> + </ul> + </div> + </div> + </div> +</header>; + + +export default function useCreditCardForm(app) { + const router = koa_router(); + app.use(router.routes()); + const koaBody = koa_body(); + + router.get('/credit_card', function *() { + console.log('-- /credit_card -->', this.session.uid, this.session.user); + const user_id = this.session.user; + if (!user_id) { this.body = 'user not found'; return; } + // const eid = yield models.Identity.findOne( + // {attributes: ['provider_user_id'], where: {user_id, provider: 'credit_card'}, order: 'id DESC'} + // ); + const body = renderToString(<div className="App"> + {header} + <br /> + <div className="row"> + <form className="column small-6" id="credit-card-form" action="/charge_credit_card" method="POST"> + <input type="hidden" name="csrf" value={this.csrf} /> + <div class="row"> + Please enter your credit card details below.<br /> + <span className="secondary">This information allows Steemit...</span> + </div> + <hr /> + <div className="row"> + <div className="small-3 columns"> + <label htmlFor="card-holder-name" className="text-right middle">Name on Card</label> + </div> + <div className="small-9 columns"> + <input type="text" name="card-holder-name" id="card-holder-name" placeholder="Card Holder's Name" /> + </div> + </div> + <div className="row"> + <div className="small-3 columns"> + <label htmlFor="card-number" className="text-right middle">Card Number</label> + </div> + <div className="small-9 columns"> + <input type="text" name="card-number" id="card-number" placeholder="Debit/Credit Card Number" /> + </div> + </div> + <div className="row"> + <div className="small-3 columns"> + <label htmlFor="expiry-month" className="text-right middle">Expiration Date</label> + </div> + <div className="small-5 columns"> + <select name="expiry-month" id="expiry-month"> + <option>Month</option> + <option value="01">Jan (01)</option> + <option value="02">Feb (02)</option> + <option value="03">Mar (03)</option> + <option value="04">Apr (04)</option> + <option value="05">May (05)</option> + <option value="06">June (06)</option> + <option value="07">July (07)</option> + <option value="08">Aug (08)</option> + <option value="09">Sep (09)</option> + <option value="10">Oct (10)</option> + <option value="11">Nov (11)</option> + <option value="12">Dec (12)</option> + </select> + </div> + <div className="small-4 columns"> + <select name="expiry-year" id="expiry-year"> + <option value="16">2016</option> + <option value="17">2017</option> + <option value="18">2018</option> + <option value="19">2019</option> + <option value="20">2020</option> + <option value="21">2021</option> + <option value="22">2022</option> + <option value="23">2023</option> + </select> + </div> + </div> + <div className="row"> + <div className="small-3 columns"> + <label htmlFor="cvv" className="text-right middle">Card CVV</label> + </div> + <div className="small-3 columns"> + <input type="text" name="cvv" id="cvv" placeholder="Security Code" /> + </div> + </div> + <div className="row"> + <div className="small-12 columns"> + <div id="form-errors" className="error">{this.flash.error}</div> + </div> + </div> + <div className="row"> + <div className="small-12 columns"> + <input type="submit" id="submit-btn" className="button float-right" value="Pay and Continue" /> + </div> + </div> + </form> + </div> + </div>); + const props = { body: Iso.render(body, {stripe_key: config.stripe.publishable_key}), title: 'Credit Card Details', assets, meta: [] }; + this.body = '<!DOCTYPE html>' + renderToString(<ServerHTML { ...props } />); + }); + + router.post('/charge_credit_card', koaBody, function *() { + console.log('-- /charge_credit_card -->', this.request.body); + if (!checkCSRF(this, this.request.body.csrf)) return; + const user_id = this.session.user; + if (!user_id) { this.body = 'user not found'; return; } + + const stripeToken = this.request.body.stripeToken; + + stripe.charges.create({card: stripeToken, currency: 'usd', amount: 100}, + (err, charge) => { + if (err) { + console.log('---> charge error:', err); + // res.send('error'); + } else { + console.log('---> charge:', charge); + // res.send('success'); + } + } + ); + // let eid = yield models.Identity.findOne( + // {attributes: ['id', 'email'], where: {user_id, provider: 'email'}, order: 'id'} + // ); + // if (eid) { + // yield eid.update({confirmation_code}); + // } else { + // eid = yield models.Identity.create({ + // provider: 'email', + // user_id, + // uid: this.session.uid, + // email, + // verified: false, + // confirmation_code + // }); + // } + this.body = 'ok'; + }); +}