├── .gitignore ├── README.md ├── app ├── __init__.py ├── forms.py ├── keys.cfg ├── models.py ├── templates │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── charge.html │ ├── login.html │ ├── members.html │ └── register.html └── views.py ├── config.py ├── db_create.py ├── error.log ├── requirements.txt └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | .Python 2 | env 3 | bin 4 | lib 5 | include 6 | .DS_Store 7 | .pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flask-stripe 2 | ======================== 3 | 4 | Use this app to integrate Flask and Stripe with a simple user registration system. After user registration, the user is taken to a memeber's page where s/he can then purchase premium content via Stripe. 5 | 6 | - Flask==0.10.1 7 | - Flask-SQLAlchemy==0.16 8 | - Flask-WTF==0.8.4 9 | - Jinja2==2.7 10 | - MarkupSafe==0.18 11 | - SQLAlchemy==0.8.2 12 | - WTForms==1.0.4 13 | - Werkzeug==0.9.1 14 | - itsdangerous==0.22 15 | - requests==1.2.3 16 | - stripe==1.9.2 17 | - wsgiref==0.1.2 18 | 19 | ## Setup 20 | 21 | 1. clone the repo 22 | 2. setup/activate a virtualenv 23 | 3. install the requirements 24 | 4. update the rdms (sqlite, mysql, postgres) 25 | 5. create the database (*db_create.py*) 26 | 6. Update the amount charged in the controller, *views.py*, and the subsequent views, *memebers.html* and *charge.html*. 27 | 28 | ## Todo 29 | 30 | 1. create better documentation 31 | 2. add unit tests 32 | 3. add email verification 33 | 4. add the ability to make a subscription purchase as well 34 | 5. create a payment form instead of the generic stripe popup 35 | 36 | ## Screenshot 37 | 38 | ![djang-stripe](http://content.screencast.com/users/Mike_Extentech/folders/Jing/media/754f9275-d5e4-4aa9-bfcf-a0f6fbebf63b/00000211.png) 39 | 40 | ## Project structure 41 | 42 | ├── app 43 | │   ├── __init__.py 44 | │   ├── forms.py 45 | │   ├── keys.cfg 46 | │   ├── models.py 47 | │   ├── templates 48 | │   │   ├── 404.html 49 | │   │   ├── 500.html 50 | │   │   ├── base.html 51 | │   │   ├── charge.html 52 | │   │   ├── login.html 53 | │   │   ├── members.html 54 | │   │   └── register.html 55 | │   └── views.py 56 | ├── config.py 57 | ├── db_create.py 58 | ├── error.log 59 | ├── requirements.txt 60 | └── run.py 61 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask.ext.sqlalchemy import SQLAlchemy 3 | 4 | app = Flask(__name__) 5 | app.config.from_object('config') 6 | db = SQLAlchemy(app) 7 | 8 | from app import views, models 9 | 10 | 11 | 12 | if not app.debug: 13 | import os 14 | import logging 15 | 16 | from logging import Formatter, FileHandler 17 | from config import basedir 18 | 19 | file_handler = FileHandler(os.path.join(basedir,'error.log')) 20 | file_handler.setFormatter(Formatter('%(asctime)s %(levelname)s: %(message)s ' 21 | '[in %(pathname)s:%(lineno)d]')) 22 | app.logger.setLevel(logging.INFO) 23 | file_handler.setLevel(logging.INFO) 24 | app.logger.addHandler(file_handler) 25 | app.logger.info('errors') -------------------------------------------------------------------------------- /app/forms.py: -------------------------------------------------------------------------------- 1 | from flask.ext.wtf import Form, TextField, PasswordField, DateField, IntegerField, SelectField 2 | from flask.ext.wtf import Required, Email, EqualTo, Length 3 | 4 | class RegisterForm(Form): 5 | name = TextField('Username', validators=[Required(), Length(min=3, max=25)]) 6 | email = TextField('Email', validators=[Required(), Length(min=6, max=40)]) 7 | password = PasswordField('Password', 8 | validators=[Required(), Length(min=6, max=40)]) 9 | confirm = PasswordField( 10 | 'Repeat Password', 11 | [Required(), EqualTo('password', message='Passwords must match')]) 12 | 13 | class LoginForm(Form): 14 | name = TextField('Username', validators=[Required()]) 15 | password = PasswordField('Password', validators=[Required()]) -------------------------------------------------------------------------------- /app/keys.cfg: -------------------------------------------------------------------------------- 1 | SECRET_KEY = 'sk_test_h36oicOrlA7ATkI9JJ6dUGyA' 2 | PUBLISHABLE_KEY = 'pk_test_8Xho4FfArFFuQspdH8V1KlHS' -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | 3 | class User(db.Model): 4 | 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String, unique=True, nullable=False) 9 | email = db.Column(db.String, unique=True, nullable=False) 10 | password = db.Column(db.String, nullable=False) 11 | 12 | 13 | def __init__(self, name=None, email=None, password=None): 14 | self.name = name 15 | self.email = email 16 | self.password = password 17 | 18 | def __repr__(self): 19 | return '' % (self.name) -------------------------------------------------------------------------------- /app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Sorry ...

4 |

There's nothing here! Scram!

5 |

Back

6 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Something's wrong!

4 |

Fortunately we are on the job, and you can just return to the login page!

5 |

Back

6 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome!! 6 | 7 | 8 | 9 | 10 | 26 | 27 | 28 |
29 |
30 | 39 |

Flask-Stripe

40 |
41 |
42 | {% for message in get_flashed_messages() %} 43 |
{{ message }}

44 | {% endfor %} {% if error %} 45 |
Error: {{ error }}
{% endif %} {% block content %} {% endblock %} 46 |
47 | 50 |
-------------------------------------------------------------------------------- /app/templates/charge.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Thanks!

4 |

You just payed $6.00 for premium content! Click here to return to the member's page.

5 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Welcome to Flask-Stripe

4 |
5 |

With this app, you register first, and then you are taken to the member's page where you can then choose to purchase premium content via Stripe.

6 |

Please login.

7 |
8 |
9 |

{{ form.name.label }} {{ form.name }}{{ form.password.label }} {{ form.password }}

10 |
11 |

Need an account? Signup!!

12 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/members.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Welcome to Flask-Stripe

4 |

This is the member's page.

5 |
6 |
7 |
8 | 11 |
12 | 17 |
18 |
19 |

Please Note: This app is running in test mode, use the following credit card information to make a purchase: 20 |

25 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Welcome to Flask-Stripe

4 |

Please register.

5 |
6 |
7 |

Username: {{ form.name }}  Email: {{ form.email }}

8 |

Password: {{ form.password }}  Verify Password: {{ form.confirm }}

9 |

10 |
11 |

Already registered? Click here to login.

12 | {% endblock %} -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from app import app, db 2 | from flask import flash, redirect, render_template, request, session, url_for 3 | from functools import wraps 4 | from app.forms import RegisterForm, LoginForm 5 | from app.models import User 6 | from sqlalchemy.exc import IntegrityError 7 | import os 8 | import stripe 9 | 10 | app.config.from_pyfile('keys.cfg') 11 | stripe_keys = { 12 | 'secret_key': app.config['SECRET_KEY'], 13 | 'publishable_key': app.config['PUBLISHABLE_KEY'] 14 | } 15 | 16 | stripe.api_key = stripe_keys['secret_key'] 17 | 18 | def flash_errors(form): 19 | for field, errors in form.errors.items(): 20 | for error in errors: 21 | flash(u"Error in the %s field - %s" % ( 22 | getattr(form, field).label.text,error), 'error') 23 | 24 | def login_required(test): 25 | @wraps(test) 26 | def wrap(*args, **kwargs): 27 | if 'logged_in' in session: 28 | return test(*args, **kwargs) 29 | else: 30 | flash('You need to login first.') 31 | return redirect(url_for('login')) 32 | return wrap 33 | 34 | @app.route('/logout/') 35 | def logout(): 36 | session.pop('logged_in', None) 37 | session.pop('user_email', None) 38 | flash('You are logged out. Bye. :(') 39 | return redirect (url_for('login')) 40 | 41 | @app.route('/') 42 | @app.route('/', methods=['GET', 'POST']) 43 | def login(): 44 | error = None 45 | if request.method=='POST': 46 | u = User.query.filter_by(name=request.form['name'], 47 | password=request.form['password']).first() 48 | if u is None: 49 | error = 'Invalid username or password.' 50 | else: 51 | session['logged_in'] = True 52 | session['user_email'] = u.email 53 | flash('You are logged in. Go Crazy.') 54 | return redirect(url_for('members')) 55 | 56 | return render_template("login.html", 57 | form = LoginForm(request.form), 58 | error = error) 59 | 60 | @app.route('/members/') 61 | @login_required 62 | def members(): 63 | return render_template('members.html', key=stripe_keys['publishable_key']) 64 | 65 | @login_required 66 | @app.route('/charge', methods=['POST']) 67 | def charge(): 68 | # Amount in cents 69 | amount = 600 70 | email = session['user_email'] 71 | 72 | customer = stripe.Customer.create( 73 | email=email, 74 | card=request.form['stripeToken'] 75 | ) 76 | 77 | charge = stripe.Charge.create( 78 | customer=customer.id, 79 | amount=amount, 80 | currency='usd', 81 | description='Flask Charge' 82 | ) 83 | 84 | return render_template('charge.html', amount=amount) 85 | 86 | @app.route('/register/', methods=['GET','POST']) 87 | def register(): 88 | error = None 89 | form = RegisterForm(request.form, csrf_enabled=False) 90 | if form.validate_on_submit(): 91 | new_user = User( 92 | form.name.data, 93 | form.email.data, 94 | form.password.data, 95 | ) 96 | try: 97 | db.session.add(new_user) 98 | db.session.commit() 99 | flash('Thanks for registering. Please login.') 100 | return redirect(url_for('login')) 101 | except IntegrityError: 102 | error = 'Oh no! That username and/or email already exist. Please try again.' 103 | else: 104 | flash_errors(form) 105 | return render_template('register.html', form=form, error=error) 106 | 107 | @app.errorhandler(500) 108 | def internal_error(error): 109 | db.session.rollback() 110 | return render_template('500.html'), 500 111 | 112 | @app.errorhandler(404) 113 | def internal_error(error): 114 | return render_template('404.html'), 404 -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | basedir = os.path.abspath(os.path.dirname(__file__)) 4 | DATABASE = 'test.db' 5 | SECRET_KEY = 'my precious' 6 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'test.db') 7 | -------------------------------------------------------------------------------- /db_create.py: -------------------------------------------------------------------------------- 1 | from config import SQLALCHEMY_DATABASE_URI 2 | from app import db 3 | 4 | db.create_all() -------------------------------------------------------------------------------- /error.log: -------------------------------------------------------------------------------- 1 | 2013-07-16 19:44:32,654 INFO: errors [in /Users/michaelherman/Desktop/flask/flasktaskr/__init__.py:24] 2 | 2013-07-16 19:45:03,082 INFO: errors [in /Users/michaelherman/Desktop/flask/flasktaskr/__init__.py:24] 3 | 2013-07-16 19:45:03,768 INFO: errors [in /Users/michaelherman/Desktop/flask/flasktaskr/__init__.py:24] 4 | 2013-07-16 20:10:21,601 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 5 | 2013-07-16 20:10:24,942 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 6 | 2013-07-16 20:10:25,190 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 7 | 2013-07-16 20:11:05,483 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 8 | 2013-07-16 20:11:07,836 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 9 | 2013-07-16 20:11:11,425 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 10 | 2013-07-16 20:11:11,709 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 11 | 2013-07-16 20:12:05,504 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 12 | 2013-07-16 20:12:17,657 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 13 | 2013-07-16 20:12:20,280 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 14 | 2013-07-16 20:12:20,541 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 15 | 2013-07-16 20:13:32,809 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 16 | 2013-07-16 20:26:10,829 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 17 | 2013-07-16 20:26:37,609 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 18 | 2013-07-16 20:26:45,997 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 19 | 2013-07-16 20:29:57,762 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 20 | 2013-07-16 20:29:58,266 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 21 | 2013-07-16 20:30:20,993 INFO: errors [in /Users/michaelherman/Desktop/flask/app/__init__.py:25] 22 | 2013-07-16 20:42:07,081 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 23 | 2013-07-16 20:42:07,429 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 24 | 2013-07-16 20:59:26,641 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 25 | 2013-07-16 20:59:26,998 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 26 | 2013-07-17 11:53:35,747 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 27 | 2013-07-17 11:53:50,153 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 28 | 2013-07-17 11:53:50,428 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 29 | 2013-07-17 11:55:11,347 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 30 | 2013-07-17 12:05:04,604 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 31 | 2013-07-17 12:05:27,146 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 32 | 2013-07-17 12:05:31,468 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 33 | 2013-07-17 12:05:31,677 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 34 | 2013-07-17 12:18:03,319 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 35 | 2013-07-17 12:18:04,056 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 36 | 2013-07-17 12:21:16,106 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 37 | 2013-07-17 12:24:23,282 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 38 | 2013-07-17 12:27:26,941 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 39 | 2013-07-17 12:37:03,620 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:26] 40 | 2013-07-17 12:37:04,036 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:26] 41 | 2013-07-17 12:38:13,109 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 42 | 2013-07-17 12:38:37,685 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 43 | 2013-07-17 12:38:50,001 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 44 | 2013-07-17 12:38:57,283 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 45 | 2013-07-17 12:39:05,584 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 46 | 2013-07-17 12:39:39,700 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 47 | 2013-07-17 12:39:39,895 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 48 | 2013-07-17 12:49:41,663 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:34] 49 | 2013-07-17 12:49:42,084 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:34] 50 | 2013-07-17 12:50:18,232 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:27] 51 | 2013-07-17 12:51:07,654 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 52 | 2013-07-17 12:51:08,088 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 53 | 2013-07-17 12:52:58,048 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 54 | 2013-07-17 12:54:08,806 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 55 | 2013-07-17 12:54:18,434 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 56 | 2013-07-17 12:54:32,815 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 57 | 2013-07-17 12:58:25,993 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 58 | 2013-07-17 13:02:09,346 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 59 | 2013-07-17 13:02:24,973 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 60 | 2013-07-17 13:04:04,630 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 61 | 2013-07-17 13:04:06,907 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 62 | 2013-07-17 13:04:07,136 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 63 | 2013-07-17 13:05:10,899 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 64 | 2013-07-17 13:06:17,714 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 65 | 2013-07-17 13:06:30,082 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 66 | 2013-07-17 13:06:47,591 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 67 | 2013-07-17 13:06:47,812 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 68 | 2013-07-17 13:09:47,751 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 69 | 2013-07-17 13:10:14,564 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 70 | 2013-07-17 13:10:14,789 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 71 | 2013-07-17 13:46:19,666 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 72 | 2013-07-17 13:46:19,997 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 73 | 2013-07-17 13:50:32,421 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 74 | 2013-07-17 14:12:40,209 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 75 | 2013-07-17 14:12:40,634 INFO: errors [in /Users/michaelherman/Desktop/flask-basic-registration/app/__init__.py:25] 76 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-SQLAlchemy==0.16 3 | Flask-WTF==0.8.4 4 | Jinja2==2.7 5 | MarkupSafe==0.18 6 | SQLAlchemy==0.8.2 7 | WTForms==1.0.4 8 | Werkzeug==0.9.1 9 | itsdangerous==0.22 10 | requests==1.2.3 11 | stripe==1.9.2 12 | wsgiref==0.1.2 13 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | app.run(debug = True) --------------------------------------------------------------------------------