store front signup with account @TODO: add simple payments like donation

legacy
Steve L. Nyemba 5 years ago
parent 3e30fd73f7
commit d5705e1eb6

@ -7,20 +7,25 @@
- Having a free product insures there is no excuse not to signup users - Having a free product insures there is no excuse not to signup users
- If a product doesn't fall under this model we will find a way to fix it. - If a product doesn't fall under this model we will find a way to fix it.
- -
The store must be prefixed with an authentication module, because customers must be signed in before making a purchase.
""" """
from __future__ import division from __future__ import division
from flask import Flask, request, session, render_template,Response from flask import Flask, request, session, render_template,Response
from flask_cors import CORS from flask_cors import CORS
import Domain # import Domain
from User import User # from User import User
from couchdbkit import Server, Document # from couchdbkit import Server, Document
import stripe import stripe
import json import json
from StringIO import StringIO from StringIO import StringIO
import re import re
import os import os
from utils.params import PARAMS from utils.params import PARAMS
from utils.transport import Couchdb, CouchdbReader from transport import factory
# from store import Store
import store
from datetime import datetime
# from utils.transport import Couchdb, CouchdbReader
PORT = 8100 if 'port' not in PARAMS else int(PARAMS['port']) ; PORT = 8100 if 'port' not in PARAMS else int(PARAMS['port']) ;
path = PARAMS['path'] #os.environ['CONFIG'] path = PARAMS['path'] #os.environ['CONFIG']
f = open(path) f = open(path)
@ -36,23 +41,71 @@ stripe.api_key = stripe_keys['secret_key'].strip()
app = Flask(__name__) app = Flask(__name__)
CORS(app) # CORS(app)
COUCHDB = Server(uri=CONFIG['couchdb']['uri']) ; # COUCHDB = Server(uri=CONFIG['couchdb']['uri']) ;
SYS_STORE = CONFIG['couchdb'] # SYS_STORE = CONFIG['couchdb']
SYS_STORE = CONFIG['store']
@app.route("/")
def index():
mystore = store.factory.instance(name='music')
products = mystore.get.products()
args = {"context":CONTEXT,"products":products}
return json.dumps(products),"content-type:application/json"
# return render_template("index.html",**args)
@app.route("/json/<id>")
def get_plans_json(id):
""" """
This function will set the user information to the session and update the information This function returns the plans for a given product
@header uid user email address
""" """
HEADER="Content-type:application/json"
try:
mystore = store.factory.instance(name=id)
return json.dumps(mystore.get.plans()),HEADER
except Exception as e:
#
# Log this shit or not
pass
return "[]",HEADER
@app.route("/ui/signup/<id>")
def signup(id):
"""
"""
mystore = store.factory.instance(name=id)
plans = mystore.get.plans()
index = int(request.args['index'])
plan = plans[index]
args = {"product":id,"label":mystore.product['statement_descriptor'],"plan":plan,"context":CONTEXT,"now":datetime.now().year}
args['theme'] = 'theme-clouds'
return render_template("signup.html",**args)
@app.route("/ui/<id>")
def get_plans_ui(id):
mystore = store.factory.instance(name=id)
#
# sort plans by
description = mystore.product['metadata']['about'] if 'about' in mystore.product['metadata'] else ''
label = mystore.product['statement_descriptor']
args = {"product":id,"label":label,"description":description,"context":CONTEXT,"plans":mystore.get.plans(),"now":datetime.now().year}
args['theme'] = 'theme-clouds'
print (mystore.product.keys())
return render_template('plans.html',**args)
@app.route("/init/<product>",methods=['POST']) @app.route("/init/<product>",methods=['POST'])
def init(product): def init(product):
""" """
This function initializes a product to a given user, This function initializes/logs a product to a given user, i.e it will create a session for users/product/plans
if the user has provided a user identifier it will be used as her primary email. The understanding is that a product may have multiple plans under it but always a free one if the user has provided a user identifier it will be used as her primary email. The understanding is that a product may have multiple plans under it but always a free one
@param uid user's email (primary) @param uid user's email (primary)
@param pid plan identifier @param pid plan identifier
""" """
#
# get product and plans
#
uid = request.headers['uid'] uid = request.headers['uid']
plan_id = request.headers['pid'] if 'pid' in request.headers else None plan_id = request.headers['pid'] if 'pid' in request.headers else None
#
# We should just pull the factory method and get a storage handler to handle the logs
#
store = dict(CONFIG['couchdb']) store = dict(CONFIG['couchdb'])
store['dbname'] = product store['dbname'] = product
user = User(stripe=stripe,store=store,product=product) user = User(stripe=stripe,store=store,product=product)
@ -193,7 +246,7 @@ def status(app_name):
@pre 'uid' in request.headers @pre 'uid' in request.headers
""" """
@app.route('/signup/<product>') @app.route('/signup/<product>')
def signup(product) : def _signup(product) :
apikey = CONFIG['stripe']['pub'].strip() apikey = CONFIG['stripe']['pub'].strip()
# #
@ -263,7 +316,10 @@ def is_customer (app_name):
return render_template('bill.html',context=CONTEXT,apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount) return render_template('bill.html',context=CONTEXT,apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount)
if __name__ == '__main__' : if __name__ == '__main__' :
#
# setup mongodb session management (not sure why)
app.debug = True ; app.debug = True ;
app.secret_key = '360-8y-[0v@t10n]+kr81v17y' app.secret_key = '360-8y-[0v@t10n]+kr81v17y'
app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024 app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024
app.run(port=PORT,threaded=True,host='0.0.0.0') app.run(port=PORT,threaded=True,host='0.0.0.0')

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,218 @@
.theme-raspberry{
background: #00B4DB; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #0083B0, #00B4DB); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #0083B0, #00B4DB); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color:#ffffff;
font-family:verdana;
font-weight:lighter;
text-transform:capitalize;
}
.theme-dark-ocean {
background: #373B44; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #4682B4, #d3d3d3); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #4682B4, #d3d3d3); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color:#f3f3f3;
}
.theme-gray {
background: #bdc3c7; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #2c3e50, #bdc3c7); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #2c3e50, #bdc3c7); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color:#f3f3f3f3;
}
.theme-clouds {
background: #ECE9E6; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #FFFFFF, #D3D3D3); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #FFFFFF, #D3D3D3); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
img {width:48px}
body{
font-family:sans-serif;
font-size:14px;
width:80%;
margin-left:10%;
}
.default {font-size:16px;
font-family: sans-serif;
}
.button {
width:auto;
padding:6px;
font-weight:bold;
border-radius:8px;
border:2px solid #4682b4;
background-color:#4682b4;
color:white;
cursor:pointer;
text-align:center;
}
.button:hover {
background-color:#ffffff;
color:#4682b4;
}
.active {
font-weight:bold;
font-family:verdana;
cursor:pointer ;
border:2px solid transparent;
padding:4px;
}
.active:hover { border-bottom:2px solid #4682B4}
.current-plan {
border-color: #20B2AA #20B2AA transparent transparent;
border-style: solid;
border-width: 15px 15px 15px 15px;
height: 0px;
width: 0px;
}
.bold { font-weight:bold}
.caption {
font-size:28px; font-weight:bold ;
font-family:sans-serif;
text-transform:capitalize;
}
.medium-caption {
font-size:18px;
font-weight:lighter;
font-family:sans-serif ;
text-transform:capitalize;
}
.plan {
padding:4px;
margin:4px;
border-radius: 4px;
border:1px solid #CAD5E0
}
.pricing {
display:grid;
grid-template-columns: repeat(auto-fit,minmax(250px, 1fr) );
/* align-content:center; */
}
.pricing-frame {
display:grid;
height:70%;
align-content:center;
}
.footer {
bottom:10px;
position:fixed;
width: 80%;
padding:8spx;
text-align:center;
display:grid;
grid-template-columns: 10% auto 10%;
grid-gap: 2px;
}
.plan .title {
display:grid;
grid-template-columns: auto 32px;
grid-gap:2px;
font-size:24px; font-family:verdana; text-transform: capitalize; border-bottom:1px solid #CAD5E0; }
.plan .feature {display:grid; grid-template-columns: 60% 40%; grid-gap:2px; margin:2px;}
.plan .feature .label {text-transform: uppercase; grid-column:1; font-size:14px;}
.plan .feature .status {font-size:20px; text-align: right; font-size:14px;}
.border{border:1px solid #CAD5E0;}
.border-bottom{ border-bottom:1px solid #CAD5E0}
.border-right{ border-right:1px solid #CAD5E0}
.border-left{ border-left:1px solid #CAD5E0}
.border-top{ border-top:1px solid #CAD5E0}
.left { float:left}
.small { font-family:verdana; font-size:11px; font-weight:lighter; color:#000000;}
.hidden {display:none}
.width-50{width:50%; padding:4px;}
.width-25{width:25%; padding:4px}
.height-48{height:48px;}
.fa-times {color:maroon; font-size:18px;}
.theme-dark-ocean .fa-check {color:green; font-size:18px}
.fa-check {color:#20B2AA; font-size:18px;}
table { border:1px solid #CAD5E0;
border-radius:8px;
-webkit-border-radius:8px;
padding:4px;
margin:4px;
}
.action {
cursor:pointer;
padding:4px;
padding-left:10px;
padding-right:10px;
border-radius:4px;
-webkit-border-radius:4px;
-moz-border-radius:4px;
font-weight:bold;
color:#4682b4;
}
.action:hover { background-color:#4682b4; color:white}
tr {font-family:sans-serif; font-size:14px;}
td {padding:4px; font-weight:lighter; padding:4px;}
.gradient{
background-image: -ms-linear-gradient(top, #008080 -120%, #FFFFFF 50%, #005757 120%);
background-image: -moz-linear-gradient(top, #008080 -120%, #FFFFFF 50%, #005757 120%);
background-image: -o-linear-gradient(top, #008080 -120%, #FFFFFF 50%, #005757 120%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(-120, #008080), color-stop(50, #FFFFFF), color-stop(120, #005757));
background-image: -webkit-linear-gradient(top, #008080 -120%, #FFFFFF 50%, #005757 120%);
background-image: linear-gradient(to bottom, #008080 -120%, #FFFFFF 50%, #005757 120%);
}
@media only screen and (min-device-width: 320px){
table {width:99%}
img {width:28px}
.medium-caption {font-size:14px}
body {font-size:12px;}
.default {font-size:12px}
.height-48{height:40px}
.width-50{width:47%; padding:4px;}
.width-25{width:24%; padding:4px}
.pricing {display:block;}
.plan .title { font-size:18px; font-family:verdana; text-transform: capitalize; border-bottom:1px solid #CAD5E0; }
.plan .feature {display:grid; grid-template-columns: 60% 40%; grid-gap:4px; margin:4px}
.plan .feature .label {font-size:12px; text-transform: uppercase; grid-column:1}
.plan .feature .status {font-size:16px; text-align: right; font-size:14px;}
.header{display:none}
.caption {font-size:20px}
}
@media only screen and (min-device-width: 768px){
body {font-size:12px}
.default {font-size:12px}
.header .logo{
display:grid;
margin:4px;
grid-template-columns: 48px auto ;
grid-gap:2px;
align-items: center;
}
.header .logo img {width:32px}
.header {display:block;}
table {width:70%; margin-left:20%}
.medium-caption {font-size:18px}
.width-50{width:50%; padding:4px;}
.width-25{width:25%; padding:4px}
.height-48{height:48px;}
.pricing {
display:grid;
grid-template-columns: repeat(auto-fit,minmax(250px, 1fr) )
}
.plan .title { padding:4px; font-size:22px; font-family:verdana; text-transform: capitalize; border-bottom:1px solid #CAD5E0; }
.plan .feature {display:grid; grid-template-columns: 60% 40%; grid-gap:4px; margin:4px; padding:4px;}
.plan .feature .label {text-transform: uppercase; grid-column:1}
.plan .feature .status {font-size:20px; text-align: right; font-size:14px;}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,123 @@
<meta charset="UTF-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1">
<link rel="shortcut icon" href="{{context}}/static/img/logo-0.png">
<link rel="stylesheet" href="{{context}}/static/css/default.css" type="text/css">
<link href="{{ context }}/static/css/fa/css/font-awesome.css" rel="stylesheet" type="text/css">
<!--
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" />
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>
-->
<script type="text/javascript" src="{{ context }}/static/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/rpc.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/utils.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/dom.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/ext/modal.js"></script>
<title style="text-transform: capitalize">{{product.replace('-',' ')}}</title>
<script>
var signup_form = function(index){
var url = '{{context}}/ui/signup/{{product}}?index=:index'.replace(/:index/,index)
var httpclient = HttpClient.instance()
httpclient.get(url,function(x){
var html = x.responseText ;
jx.modal.show({html:html})
})
}
</script>
<body >
<div class="header border-bottom">
<div class="logo">
<div>
<img src="{{context}}/static/img/logo-0.png" align="left" style="margin:4px">
</div>
<div>
<div class="caption">The Phi Technology</div>
</div>
</div>
</div>
<div class="pricing-frame">
<div align="center" class="" style="padding:8px">
<p><div class="caption">{{label}}</div>
<div class="" style="text-transform:capitalize">{{description}}</div>
</p>
</div>
<div class="pricing">
{% for item in plans%}
<div class="plan {{theme}}">
<div class="title" align="center">
<div>{{ item.nickname }}</div>
{% if item.id in active_plans %}
<div class="current-plan"></div>
{% else %}
<div></div>
{% endif %}
{% if item.amount == 0%}
<div class="default">
<span class="bold">Free</span>
</div>
{%else%}
<div class="default" >
<span class="bold">$ {{ item.amount/100 }}</span>
/ <span class="small">{{item.interval[:]}}</span>
</div>
{% endif %}
</div>
<div class="">
{% for key,value in item.metadata['features'].iteritems() %}
<div class="feature">
<div class="label">{{ key }}</div>
{% if value == True %}
<div class="status"><i class="fa fa-check"></i></div>
{% elif value == False %}
<div class="status"><i class="fa fa-times"></i></div>
{%else %}
<div class="status">{{value}}</div>
{% endif %}
</div>
{% endfor %}
</div>
<div align="right" class="border-top">
<p>
<div class="button " onclick="signup_form({{loop.index-1}})">
Signup Now
</div>
</p>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="small footer">
<div>support@the-phi.com</div>
<div>
all rights reserved &copy; {{ now }}, The Phi Technology
</div>
<div>
Privacy & Terms
</div>
</div>
</body>

@ -0,0 +1,141 @@
<meta charset="UTF-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1">
<link rel="shortcut icon" href="{{context}}/static/img/logo-0.png">
<link rel="stylesheet" href="{{context}}/static/css/default.css" type="text/css">
<link href="{{ context }}/static/css/fa/css/font-awesome.css" rel="stylesheet" type="text/css">
<!--
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" />
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>
-->
<script type="text/javascript" src="{{ context }}/static/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/rpc.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/utils.js"></script>
<script type="text/javascript" src="{{ context }}/static/js/jx/dom.js"></script>
<title style="text-transform: capitalize">Signup {{product.replace('-',' ')}}</title>
<style>
.pane {
display:grid;
align-content: center;
grid-gap:1px;
grid-template-columns: 50% auto;
width:auto;
}
.border-round {
border-radius:8px;
-webkit-border-radius:8px;
-moz-border-radius:8px;
padding:8px;
}
.border-none {border:1px solid transparent}
.signup-button {
display:flex;
align-items:center;
padding:4px;
border:2px solid #CAD5E0;
border-radius:4px;
margin:2px;
height:30px;
font-size:14px;
width:185px;
cursor:pointer;
background-color:#FFFFFF;
color:#000000;
}
.signup-button img {
margin:4px;
width:30px;
background:#ffffff;
margin-right:15px;
}
.signup-button span {font-weight:bold}
.signup-button:hover {
color:#4682b4;
background-color:#ffffff;
border-color:#4682b4
}
.signup-button:hover i{
color:#000000;
}
.title {
display:grid;
grid-gap:2px;
grid-template-columns: 32px auto;
align-items: center;
}
</style>
<body>
<p></p>
<div class="pane border-round {{theme}}" >
<div class=" border-right">
<div class="title">
<div>
<i class="fa fa-check fa-2x"></i>
</div>
<div>
<div class="caption">{{label}}</div>
<div class="default bold" style="text-transform:capitalize">
<span class="">{{plan.nickname}} Plan, </span>
<span class="bold ">{{plan.amount / 100}} </span> / <span style="text-transform: lowercase; font-weight:lighter">{{plan.interval}}</span>
</div>
</div>
</div>
<p>
<div class="plan border-none " >
{% for key,value in plan.metadata.features.iteritems() %}
<div class="feature">
<div class="label">
{{key}}
</div>
<div class="status">
{% if value == 1%}
<i class="fa fa-check"></i>
{% elif value == 0%}
<i class="fa fa-times"></i>
{% else %}
{{ value}}
{% endif %}
</div>
</div>
{% endfor%}
</div>
</p>
</div>
<div class="" >
<div class="medium-caption" align="center">Signup using a cloud account</div>
<p>
<div align="center" style="display:grid; align-content:center; justify-content:center" >
<input type="image" src="{{context}}/static/assets/google/normal.png" style="height:48px;" data-value="google-drive"/>
<div class="signup-button" data-value="dropbox">
<img src="{{context}}/static/img/accounts/dropbox.png">
<span>Dropbox</span>
</div>
<div class="border-left border-bottom signup-button" data-value="one-drive">
<img src="{{context}}/static/img/accounts/microsoft.png"> <span>One Drive</span>
</div>
<div class="border-left border-bottom signup-button" data-value="box">
<img src="{{context}}/static/img/accounts/box.png"> <span>Box</span>
</div>
</div>
</p>
</div>
</div>
</body>
Loading…
Cancel
Save