enhancements, and bug fixes: no multiple subscriptions and update of plans

legacy
Steve L. Nyemba 6 years ago
parent 33ec166792
commit b41b1556be

@ -7,7 +7,7 @@ install(){
} }
start(){ start(){
sandbox/bin/python src/api/index.py --path $PWD/config.json --port 8084 --context store & > log sandbox/bin/python src/api/index.py --path $PWD/config.json --port 8084 --context store & > log
} }
stop(){ stop(){

@ -14,8 +14,8 @@ class User :
self.stripe = args["stripe"] self.stripe = args["stripe"]
self.store = args['store'] self.store = args['store']
self.store['dbname'] = args['product'] self.store['dbname'] = str(args['product'])
self.product = args['product'] self.product = str(args['product'])
self.me = {} self.me = {}
self.init() self.init()
@ -25,10 +25,13 @@ class User :
lproducts = [item for item in lproducts.auto_paging_iter() if item.name == self.product ] lproducts = [item for item in lproducts.auto_paging_iter() if item.name == self.product ]
if lproducts : if lproducts :
self.me['info'] = {"active":lproducts[0].active,"id":lproducts[0].id,"description":lproducts[0].statement_descriptor,"images":lproducts[0].images} self.me['info'] = {"active":lproducts[0].active,"id":lproducts[0].id,"description":lproducts[0].statement_descriptor,"images":lproducts[0].images}
self.plans = self.stripe.Plan.list(product=lproducts[0].id)
self.plans = [item for item in self.plans.auto_paging_iter() ]
def init_customer(self,uid): def init_customer(self,uid,pid):
""" """
This function copies a customer to the internal database This function copies a customer to the internal database.
This would allow the object to be in sync with what is available in stripe and we will save the content later on ...
""" """
customer = self.stripe.Customer.list(email=uid) customer = self.stripe.Customer.list(email=uid)
if customer : if customer :
@ -36,20 +39,40 @@ class User :
customer = customer[0] customer = customer[0]
# #
# Insure the product is the one we are looking for ... # Insure the product is the one we are looking for ...
args = dict(self.store) args = dict(self.store)
args['uid']=uid
reader = CouchdbReader(**args) reader = CouchdbReader(**args)
key = reader.view('users/uid_map',key=uid) key = reader.view('users/uid_map',key=uid)
if not key : if not key :
self.store['uid'] = customer.id self.store['uid'] = customer.id
self.update(_id=customer.id,emails=[uid]) self.update(_id=customer.id,emails=[uid])
#
# We need to update the user's subscription
#
product_id = self.me['info']['id']
info = {}
lsub = customer.subscriptions ;
info = {}
found = False
for sub in lsub.data :
if sub.plan.id == pid or sub.plan.product == product_id:
info[sub.plan.nickname] = sub.plan
self.update(subscriptions=info)
self.post() self.post()
#-- housekeeping work #-- housekeeping work
else:
return customer #
# The key exists we need to just read the user info in me
reader.uid = key
document = reader.read()
self.me = dict(self.me,**document)
self.user_key = customer.id
return customer
else: else:
return None return None
def update(self,**args): def update(self,**args):
for key in args : for key in args :
@ -66,19 +89,34 @@ class User :
self.me[key] = value self.me[key] = value
def post(self,**args): def post(self,**args):
document = dict(self.me,**{})
args = dict(self.store) args = dict(self.store)
args['create'] = True args['create'] = True
writer = CouchdbWriter(**args) writer = CouchdbWriter(**args)
writer.set(self.me) writer.set(document)
# writer.close() # writer.close()
def get_key(self,uid):
reader = CouchdbReader(**self.store)
key = reader.view('users/uid_map',key=uid)
if key :
if isinstance(key,list) :
key = key[0]['value']
elif 'value' in key :
key = key['value']
else:
key = None
return key
def get(self,uid,key): def get(self,uid,key):
args = dict(self.store) args = dict(self.store)
args['uid'] = 'logs' args['uid'] = self.get_key(uid)
couchdb = CouchdbReader(**args) if key not in self.me :
document = couchdb.view('users/uid_map',key=uid) couchdb = CouchdbReader(**args)
return document document = couchdb.basic_read()
return document[key] if key in document else {}
else:
return self.me[key]
# def plans(self,free=False): # def plans(self,free=False):
# lproducts = self.stripe.Product.list() # lproducts = self.stripe.Product.list()
# plans = [item for item in lproducts.auto_paging_iter() if item.name == self.product ] # plans = [item for item in lproducts.auto_paging_iter() if item.name == self.product ]
@ -91,22 +129,14 @@ class User :
@param pid plan id @param pid plan id
@param stripeToken stripe token to process payments @param stripeToken stripe token to process payments
""" """
# customer = self.stripe.Customer.list(email=uid).data customer = self.init_customer(uid,pid)
# if customer : product_id = self.me['info']['id']
# customer = [item for item in customer if item.email == uid]
# customer = customer[0]
#
customer = self.init_customer(uid)
if pid is None : if pid is None :
# #
# In this block we try to find the free plan if one isn't specified # In this block we try to find the free plan if one isn't specified
# The informtion about the product is already known (constructor) # The informtion about the product is already known (constructor)
# #
# lproducts = self.stripe.Product.list() plans = [item for item in self.plans if item.amount == 0]
# lproducts = [item for item in lproducts.auto_paging_iter() if item.name == self.product ]
product_id = self.me['info']['id']
plans = self.stripe.Plan.list(product=product_id)
plans = [item for item in plans.auto_paging_iter() if item.amount == 0 ]
pid = None if not plans else plans[0].id pid = None if not plans else plans[0].id
if not customer : if not customer :
@ -120,29 +150,14 @@ class User :
if stripeToken: if stripeToken:
args['source'] = stripeToken args['source'] = stripeToken
if customer.subscriptions.data : sub = self.me['subscriptions']
lsub = customer.subscriptions ; found = len([1 for plan_key in sub if sub[plan_key]['id'] == pid]) > 0
found = False
for sub in lsub.data :
if sub.plan.id == pid :
found = True
break
# for sub in lsub :
# found = [ for item in lsub.d
# found = [plan for plan in customer.subscriptions.data if plan.id == pid and plan.active == True]
# print " found ",len(found) > 0
else:
found = False
if found is False : if found is False :
sub = self.stripe.Subscription.create(**args) sub = self.stripe.Subscription.create(**args)
info = {sub.plan.nickname:sub.plan} info = {sub.plan.nickname:sub.plan}
self.update(subscriptions=info) self.update(subscriptions=info)
self.post()
else: self.post()
pass
# #
# keep a copy of this on our servers ... # keep a copy of this on our servers ...
# #
@ -153,23 +168,29 @@ class User :
customer = parent.stripe.Customer.retrieve(customer_id) customer = parent.stripe.Customer.retrieve(customer_id)
parent.store['uid'] = customer.id parent.store['uid'] = customer.id
lsub = customer.subscriptions lsub = customer.subscriptions
reader = CouchdbReader(**parent.store) reader = CouchdbReader(**parent.store)
parent.me = reader.read() _me = reader.read()
if 'emails' not in parent.me :
parent.me['emails'] = []
emails = list(set(parent.me['emails']) | set(_me['emails']))
parent.me = dict(parent.me,**_me)
parent.me['emails'] = emails
product_id = parent.me['info']['id'] product_id = parent.me['info']['id']
[parent.update(subscriptions={sub.plan.nickname:sub.plan}) for sub in lsub.auto_paging_iter() if sub.plan.product == product_id and sub.plan.active == True] [parent.update(subscriptions={sub.plan.nickname:sub.plan}) for sub in lsub.auto_paging_iter() if sub.plan.product == product_id and sub.plan.active == True]
parent.post() parent.post()
reader = CouchdbReader(**self.store) key = self.get_key(uid)
key = reader.view('users/uid_map',key=uid) # reader = CouchdbReader(**self.store)
# key = reader.view('users/uid_map',key=uid)
# if key :
# if isinstance(key,list) :
# key = key[0]['value']
# elif 'value' in key :
# key = key['value']
if key : if key :
if isinstance(key,list) :
key = key[0]['value']
elif 'value' in key :
key = key['value']
thread = Thread(target=_update,args=(key,)) thread = Thread(target=_update,args=(key,))
thread.start() thread.start()

@ -12,6 +12,7 @@ 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 couchdbkit import Server, Document from couchdbkit import Server, Document
import stripe import stripe
import json import json
@ -37,6 +38,7 @@ 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']
""" """
This function will set the user information to the session and update the information This function will set the user information to the session and update the information
@header uid user email address @header uid user email address
@ -49,18 +51,30 @@ def init(product):
@param uid user's email (primary) @param uid user's email (primary)
@param pid plan identifier @param pid plan identifier
""" """
email = request.headers['uid'] uid = request.headers['uid']
plan_id = request.headers['pid'] plan_id = request.headers['pid'] if 'pid' in request.headers else None
user = User(stripe=stripe,store=CONFIG['couchdb'],product=product) user = User(stripe=stripe,store=CONFIG['couchdb'],product=product)
user.susbscribe(uid,plan_id) user.subscribe(uid,plan_id)
sub = None
if 'auid' in request.headers : if 'auid' in request.headers :
auid = request.header['auid'] auid = request.header['auid']
user.update(emails=auid) user.update(emails=auid)
user.post() user.post()
features = json.loads(users.me['subscriptions'][pid][0]['metadata'])
else:
features = user.get(uid,key) store = dict(CONFIG['couchdb'],**{})
user.refresh() store['dbname'] = product
store['uid'] = 'logs'
sub = user.get(uid,'subscriptions')
features = {}
for id in sub :
if sub[id]['active'] is True :
features[id] = sub[id]
user.refresh(uid)
session['key'] = user.user_key
session['uid'] = uid
return json.dumps(features),200 return json.dumps(features),200
# @app.route("/init/<app_name>",methods=['POST']) # @app.route("/init/<app_name>",methods=['POST'])
@ -109,16 +123,16 @@ def init(product):
""" """
This function will update the user's email This function will update the user's email
""" """
@app.route('/init/<app_name>',methods=['PUT']) # @app.route('/init/<app_name>',methods=['PUT'])
def init_update(app_name): # def init_update(app_name):
uid = request.headers['uid'] # uid = request.headers['uid']
id = session['customer.id'] # id = session['customer.id']
couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=None) # couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=None)
DB = couchdb.dbase # DB = couchdb.dbase
handler = Domain.User(DB,stripe) # handler = Domain.User(DB,stripe)
handler.update_user(uid=uid,id=id) # handler.update_user(uid=uid,id=id)
return ('',204) # return ('',204)
""" """
@ -127,12 +141,13 @@ def init_update(app_name):
@resource name name of the application {cloud-music} @resource name name of the application {cloud-music}
@header key service/plan @header key service/plan
""" """
@app.route('/subscribe/<app_name>',methods=['POST']) @app.route('/subscribe/<product>',methods=['POST'])
def subscribe(app_name): def subscribe(product):
# #
# The name is the full name of the service # The name is the full name of the service
# #
resp = "0" resp = "0"
user = User(stripe=stripe,store=CONFIG['couchdb'],product=product)
if 'stripeToken' in request.form : if 'stripeToken' in request.form :
stripeToken = request.form['stripeToken'] stripeToken = request.form['stripeToken']
uid = request.form['stripeEmail'] uid = request.form['stripeEmail']
@ -141,43 +156,73 @@ def subscribe(app_name):
pid = request.form['plan'] pid = request.form['plan']
else: else:
pid = request.headers['pid'] pid = request.headers['pid'] if 'pid' in request.headers else None
uid = request.headers['uid'] uid = request.headers['uid']
stripeToken = None stripeToken = None
user.subscribe(uid,pid,stripeToken)
if 'auid' in request.headers :
if request.headers['auid'].startswith('[') or request.headers['auid'].startswith("{") :
auid = json.loads(request.headers['auid'])
else:
auid = [request.headers['auid']]
user.update(emails=auid)
user.refresh(uid)
plans = stripe.Plan.list().data reader = CouchdbReader(uri=SYS_STORE['uri'],dbname=product,uid=uid,create=False)
plan = [item for item in plans if item.id == pid] plans = reader.view('users/active_plan',key=user.user_key) #me['_id'])
resp = "0" #session['plans'] = plans
session['key'] = user.user_key
session['uid'] = uid
couch_handler = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid) return (json.dumps(plans),200)
DB = couch_handler.dbase
handler = Domain.User(DB,stripe,stripeToken) ;
if plan : def get_plans(product) :
handler.init(uid,plan) lproducts = stripe.Product.list()
resp = plan[0].id lproducts = [item for item in lproducts.auto_paging_iter() if item.name == product ]
if lproducts :
product_id = lproducts[0].id
alias = lproducts[0].statement_descriptor
plans = stripe.Plan.list(product=product_id)
i = 0
for plan in plans :
plan['product_alias'] = alias if alias is not None else ''
if 'features' in plan['metadata']:
plan['metadata']['features'] = json.loads(plan['metadata']['features'])
plans[i] = plan
i += 1
if plans :
plans = plans.data
plans.sort(key = lambda x:x.amount)
return plans
else:
return []
return ('',204) @app.route('/features/<product>')
def features(product):
"""
This function returns the plan/features of a user for a given application if provided a uid (email)
if no uid are provided then the function will return all the plans/features associated with the given product
@header uid user's email
"""
"""
This function returns the features of a user for a given application
The features are returned provided the plan is still valid i.e (end_date,status={active|trailing})
@header uid user's email
"""
@app.route('/features/<app_name>')
def features(app_name):
plans = [] plans = []
if 'uid' in request.headers : if 'uid' in request.headers :
uid = request.headers['uid'] uid = request.headers['uid']
couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=product,uid=uid,create=False)
couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) key = couchdb.view("users/uid_map",key=uid)
handler = Domain.User(couchdb.dbase,stripe) if key :
handler.initialize(uid) key = key[0]['value']
lsub = handler.subscriptions() plans = couchdb.view('users/active_plan',key=key)
plans = [plan['value'] for plan in plans if 'value' in plan]
plans = [ sub['plan'] for sub in lsub if sub['ended_at'] is None ] else:
plans = [item['metadata'] for item in plans if item['id']] plans = []
else:
plans = get_plans(product)
#
# formatting plans for the output
#
return json.dumps(plans) return json.dumps(plans)
""" """
This function returns a user's plans/status for an application This function returns a user's plans/status for an application
@ -199,48 +244,31 @@ def status(app_name):
It will allow user's to be able to signup for various plans It will allow user's to be able to signup for various plans
@pre 'uid' in request.headers @pre 'uid' in request.headers
""" """
@app.route('/signup/<app_name>') @app.route('/signup/<product>')
def get_plans(app_name) : def signup(product) :
apikey = CONFIG['stripe']['pub'].strip() apikey = CONFIG['stripe']['pub'].strip()
# #
# This function returns the plans for a given application # This function returns the plans for a given application
# We assume the application name is the prefix of the plan identifier in stripe # We assume the application name is the prefix of the plan identifier in stripe
# #
uid = session['uid'] if 'uid' in session else None if 'user-info' in session :
_info = session['user-info']
session['uid'] = _info['uid']
uid = session['uid'] if 'uid' in session else ''
can_purchase = True can_purchase = True
if uid is None : if uid is None :
uid = request.args.get('uid') if 'uid' in request.args else None uid = request.args.get('uid') if 'uid' in request.args else None
plans = stripe.Plan.list(limit=10) plans = get_plans(product)
# plans = handler.cast(plans.data)
plans = json.loads(json.dumps(plans.data, sort_keys=True, indent=2))
plans = [item for item in plans if re.match(app_name,item['name'])]
if uid is not None:
couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False)
handler = Domain.User(couchdb.dbase,stripe)
if handler.getId(uid) is not None:
handler.init(uid,[])
subs = handler.subscriptions().data
ids = [item.id for item in subs]
for item in plans :
if item['id'] in ids :
item['active'] = True
#
# let's determine if this user can make a purchase
#
else:
# #
# This should address any case of the database of customers being out of sync
# @TODO: Have a log suggesting a utility imports the customer
#
return json.dumps(plans)
# #
# @TODO: Mark the plans the current user is signed up for # @TODO: Mark the plans the current user is signed up for
# #
return render_template('subscribe.html',context=CONTEXT,uid=uid,app_name=app_name,plans=plans,apikey=apikey) platform='web' if 'platform' not in request.args else request.args['platform']
alias = plans[0]['product_alias']
active_plan = session['plans'] if 'plans' in session else []
print [' ** ',uid,active_plan]
return render_template('subscribe.html',context=CONTEXT,uid=uid,alias=alias,platform=platform,app_name=product,plans=plans,apikey=apikey)
@app.route('/subscribe/<name>',methods=['DELETE']) @app.route('/subscribe/<name>',methods=['DELETE'])
def cancel_subscribe(name) : def cancel_subscribe(name) :
@ -279,4 +307,5 @@ def is_customer (app_name):
if __name__ == '__main__' : if __name__ == '__main__' :
app.debug = True ; app.debug = True ;
app.secret_key = '360-8y-[0v@t10n]+kr81v17y' app.secret_key = '360-8y-[0v@t10n]+kr81v17y'
app.run(port=PORT,threaded=True) app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024
app.run(port=PORT,threaded=True,host='0.0.0.0')

@ -0,0 +1 @@
Subproject commit df17531499104b155e5ac97ee2755a8bf10bf8ff

@ -8,9 +8,13 @@
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.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="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.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">{{app_name.replace('-',' ')}}</title> <title style="text-transform: capitalize">{{app_name.replace('-',' ')}}</title>
<style> <style>
img {width:48px}
body{ body{
font-family:sans-serif; font-family:sans-serif;
font-size:14px; font-size:14px;
@ -28,6 +32,20 @@
font-family:sans-serif ; font-family:sans-serif ;
text-transform:capitalize; 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) )
}
.plan .title { 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:4px; margin:4px}
.plan .feature .label {text-transform: uppercase; grid-column:1}
.plan .feature .status {font-size:20px; text-align: right}
.border-bottom{ border-bottom:1px solid #CAD5E0} .border-bottom{ border-bottom:1px solid #CAD5E0}
.border-right{ border-right:1px solid #CAD5E0} .border-right{ border-right:1px solid #CAD5E0}
.border-left{ border-left:1px solid #CAD5E0} .border-left{ border-left:1px solid #CAD5E0}
@ -37,7 +55,8 @@
.width-50{width:50%; padding:4px;} .width-50{width:50%; padding:4px;}
.width-25{width:25%; padding:4px} .width-25{width:25%; padding:4px}
.height-48{height:48px;} .height-48{height:48px;}
.fa-times {color:maroon}
.fa-check {color:green}
table { border:1px solid #CAD5E0; table { border:1px solid #CAD5E0;
border-radius:8px; border-radius:8px;
-webkit-border-radius:8px; -webkit-border-radius:8px;
@ -70,44 +89,66 @@
} }
@media only screen and (min-device-width: 320px){ @media only screen and (min-device-width: 320px){
table {width:99%} table {width:99%}
img {width:28px}
.medium-caption {font-size:14px} .medium-caption {font-size:14px}
body {font-size:12px;} body {font-size:12px;}
.height-48{height:40px} .height-48{height:40px}
.width-50{width:47%; padding:4px;} .width-50{width:47%; padding:4px;}
.width-25{width:24%; 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}
.header{display:none}
.caption {font-size:20px}
} }
@media only screen and (min-device-width: 768px){ @media only screen and (min-device-width: 768px){
body {font-size:12px} body {font-size:12px}
.header img {width:48px}
.header {display:block}
table {width:70%; margin-left:20%} table {width:70%; margin-left:20%}
.medium-caption {font-size:18px} .medium-caption {font-size:18px}
.width-50{width:50%; padding:4px;} .width-50{width:50%; padding:4px;}
.width-25{width:25%; padding:4px} .width-25{width:25%; padding:4px}
.height-48{height:48px;} .height-48{height:48px;}
.pricing {
display:grid;
grid-template-columns: repeat(auto-fit,minmax(250px, 1fr) )
}
.plan .title { 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:4px; margin:4px}
.plan .feature .label {text-transform: uppercase; grid-column:1}
.plan .feature .status {font-size:20px; text-align: right}
} }
</style> </style>
<body> <script>
</script>
<body >
<div> <div>
{{active_plan}}
</div> </div>
<br> <br>
<div id="grid"> <table align="center" class="header" style="border:1px solid transparent">
<table align="center" style="border-color:transparent">
<tr> <tr>
<td style="width:60%"> <td style="width:80%" valign="bottom">
<div align="left" class="default"> <div align="left">
<img src="{{context}}/static/img/logo-0.png" style="width:48px; margin:4px" class="left" style="margin:4px"> <img src="{{context}}/static/img/logo-0.png" align="left" style="margin:4px">
<div style="padding:8px">The Phi Technology <div style="padding:8px" >
<div class="small" style="text-transform:capitalize">{{ app_name }}</div> <div class="caption">The Phi Technology</div>
</div> <div class="small" style="text-transform:capitalize">{{ alias }}</div>
</div> </div>
</div>
</td> </td>
<td class="small" valign="middle"> <td align="right" class="small" valign="bottom">
{{uid}}
<div>Call : 615-866-0454</div>
<div>Email: support@the-phi.com</div>
</td> </td>
</tr> </tr>
</table> </table>
@ -116,76 +157,72 @@
<!-- style="background-image:url(https://az616578.vo.msecnd.net/files/responsive/cover/main/desktop/2016/05/09/635983505329496433385654456_concert-audience.jpg)"> --> <!-- style="background-image:url(https://az616578.vo.msecnd.net/files/responsive/cover/main/desktop/2016/05/09/635983505329496433385654456_concert-audience.jpg)"> -->
<tr > <tr >
<td colspan="{{ plans|length}}" align="center" class="border-bottom"> <td colspan="{{ plans|length}}" align="center" class="border-bottom">
<div class="caption">{{app_name.replace('-',' ') }}</div> {% if alias %}
<div class="caption">{{alias.replace('-',' ') }}</div>
{% else %}
<div class="caption">{{app_name.replace('-',' ') }}</div>
{% endif %}
<div class="small">Available Plans</div> <div class="small">Available Plans</div>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<div class="pricing">
{% for item in plans%} {% for item in plans%}
<div class="left width-50 height-48 border-right border-bottom" style="margin:4px"> <div class="plan">
<div class="bold medium-caption">{{ item.metadata.info|safe }}</div> <div class="title">{{ item.nickname }}</div>
{% if 'note' in item.metadata %} {% for key,value in item.metadata['features'].iteritems() %}
<div class="small" style="text-transform:capitalize; color:gray">{{item.metadata.note|safe}}</div> <div class="feature">
{% endif %} <div class="label">{{ key }}</div>
{% if value == 1 %}
</div> <div class="status"><i class="fa fa-check"></i></div>
<div class="left width-25 height-48 border-bottom" style="margin:4px"> {% else %}
{% if item.amount > 0 %} <div class="status"><i class="fa fa-times"></i></div>
<div class="" align="center">{{ item.amount / 100 }}</div> {% endif %}
<div class="small" align="center"> {{item.currency.upper() }}/{{ item.interval.replace('month','Mo') }}</div>
{% else %}
<div class="bold medium-caption" align="center">Free</div>
{% endif %}
</div>
<div class="left height-48 border-left border-bottom" style="margin:4px; padding:4px; width:15%" align="center">
{% if item.amount > 0 or item.active == True %}
<div class="action hidden" align="center">
<i class="fa fa-credit-card fa-2x" ></i>
</div> </div>
<div align="center">
{% endfor %}
{% if item.amount > 0 and platform != 'mobile' %}
<div align="right">
<form action="{{context}}/subscribe/{{app_name|safe}}" method="POST"> <form action="{{context}}/subscribe/{{app_name|safe}}" method="POST">
<script <script
src="https://checkout.stripe.com/checkout.js" class="stripe-button action" src="https://checkout.stripe.com/checkout.js" class="stripe-button action"
data-key="{{apikey|safe}}" data-key="{{apikey|safe}}"
data-email="{{uid|safe}}" data-email="{{uid|safe}}"
data-amount={{item.amount|safe}} data-amount={{item.amount|safe}}
data-name="{{item.metadata.info}}" data-name="{{item.nickname}}"
data-description="{{app_name }}" data-description="{{alias }}"
data-label="Signup" data-label="Signup $ {{item.amount/100 }} / {{item.interval[:2]}}"
data-image="https://s3.amazonaws.com/stripe-uploads/acct_15kiA1EUWsmgY81Amerchant-icon-1493912370912-logo-0.png" data-image="https://s3.amazonaws.com/stripe-uploads/acct_15kiA1EUWsmgY81Amerchant-icon-1493912370912-logo-0.png"
data-locale="auto"> data-locale="auto">
</script> </script>
<input type="hidden" name="amount" value="{{item.amount|safe}}"/> <input type="hidden" name="amount" value="{{item.amount|safe}}"/>
<input type="hidden" name="plan" value="{{item.id|safe}}"/> <input type="hidden" name="plan" value="{{item.id|safe}}"/>
</form> </form>
</div> </div>
{% else %} {% elif item.amount == 0 and platform != 'mobile' %}
<div align="center" class="left" style="margin:4px"><i class="fa fa-check fa-2x" style="color:green"></i> </div> <br><div align="right" style="float:right; color:#4682B4; font-weight:bold;">Login, it's Free</div>
{% if item.amount > 0 %} {% endif%}
<div align="center" class="left" style="margin:4px"><i class="fa fa-times fa-2x" style="color:maroon"></i> </div>
{% endif %}
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div>
</td> </td>
</tr> </tr>
<tr> <tr>
<td align="center" valign="middle" class="border-top"> <td align="center" valign="middle" class="border-top">
<div>Your first subscription should be enabled Online</div>
<div>https://the-phi.com/store/subscribe</div> <div class="small">support@the-phi.com</div>
</td> </td>
</tr> </tr>
</table> </table>
<br>
</div>
</body> </body>

@ -261,6 +261,7 @@ class MessageQueue:
self.close() self.close()
return resp return resp
def close(self): def close(self):
if self.connection.is_closed == False :
self.channel.close() self.channel.close()
self.connection.close() self.connection.close()
""" """
@ -351,7 +352,6 @@ class QueueReader(MessageQueue,Reader):
self.durable = False self.durable = False
self.size = -1 self.size = -1
self.data = {} self.data = {}
def init(self,qid): def init(self,qid):
properties = pika.ConnectionParameters(host=self.host) properties = pika.ConnectionParameters(host=self.host)
@ -368,6 +368,7 @@ class QueueReader(MessageQueue,Reader):
""" """
def callback(self,channel,method,header,stream): def callback(self,channel,method,header,stream):
r = [] r = []
if re.match("^\{|\[",stream) is not None: if re.match("^\{|\[",stream) is not None:
r = json.loads(stream) r = json.loads(stream)
@ -399,9 +400,12 @@ class QueueReader(MessageQueue,Reader):
# We enabled the reader to be able to read from several queues (sequentially for now) # We enabled the reader to be able to read from several queues (sequentially for now)
# The qid parameter will be an array of queues the reader will be reading from # The qid parameter will be an array of queues the reader will be reading from
# #
if isinstance(self.qid,basestring) :
self.qid = [self.qid]
for qid in self.qid: for qid in self.qid:
self.init(qid) self.init(qid)
# r[qid] = [] # r[qid] = []
if self.info.method.message_count > 0: if self.info.method.message_count > 0:
self.channel.basic_consume(self.callback,queue=qid,no_ack=False); self.channel.basic_consume(self.callback,queue=qid,no_ack=False);
@ -420,7 +424,7 @@ class QueueListener(QueueReader):
self.channel = self.connection.channel() self.channel = self.connection.channel()
self.channel.exchange_declare(exchange=self.uid,type='direct',durable=True ) self.channel.exchange_declare(exchange=self.uid,type='direct',durable=True )
self.info = self.channel.queue_declare(exclusive=True,queue=qid) self.info = self.channel.queue_declare(passive=True,exclusive=True,queue=qid)
self.channel.queue_bind(exchange=self.uid,queue=self.info.method.queue,routing_key=qid) self.channel.queue_bind(exchange=self.uid,queue=self.info.method.queue,routing_key=qid)
#self.callback = callback #self.callback = callback
@ -470,8 +474,6 @@ class Couchdb:
create = args['create'] if 'create' in args else False create = args['create'] if 'create' in args else False
if self.dbase.doc_exist(self.uid) == False and create == True: if self.dbase.doc_exist(self.uid) == False and create == True:
self.dbase.save_doc({"_id":self.uid}) self.dbase.save_doc({"_id":self.uid})
def view(self,id,**args):
return self.dbase.view(id,**args).all()
""" """
Insuring the preconditions are met for processing Insuring the preconditions are met for processing
""" """
@ -484,9 +486,14 @@ class Couchdb:
# We are also sure that the database actually exists # We are also sure that the database actually exists
# #
q = self.dbase.doc_exist(self.uid) q = self.dbase.doc_exist(self.uid)
return p and q if q == False:
return False
return True
def view(self,id,**args):
r =self.dbase.view(id,**args)
r = r.all()
return r[0]['value'] if len(r) > 0 else []
""" """
This function will read an attachment from couchdb and return it to calling code. The attachment must have been placed before hand (otherwise oops) This function will read an attachment from couchdb and return it to calling code. The attachment must have been placed before hand (otherwise oops)
@T: Account for security & access control @T: Account for security & access control
@ -518,13 +525,14 @@ class CouchdbReader(Couchdb,Reader):
# #
doc = self.dbase.get(self.uid) doc = self.dbase.get(self.uid)
q = doc is not None
if '_attachments' in doc: if '_attachments' in doc:
r = self.filename in doc['_attachments'].keys() r = self.filename in doc['_attachments'].keys()
else: else:
r = True r = False
return p and q and r return r
def stream(self): def stream(self):
content = self.dbase.fetch_attachment(self.uid,self.filename).split('\n') ; content = self.dbase.fetch_attachment(self.uid,self.filename).split('\n') ;
i = 1 i = 1
@ -555,7 +563,8 @@ class CouchdbWriter(Couchdb,Writer):
@param dbname database name (target) @param dbname database name (target)
""" """
def __init__(self,**args): def __init__(self,**args):
Couchdb.__init__(self,**args)
Couchdb.__init__(self,**args)
uri = args['uri'] uri = args['uri']
self.uid = args['uid'] self.uid = args['uid']
if 'filename' in args: if 'filename' in args:
@ -571,27 +580,53 @@ class CouchdbWriter(Couchdb,Writer):
def set(self,document): def set(self,document):
_document = self.dbase.get(self.uid) _document = self.dbase.get(self.uid)
if _document : document = dict(document,**_document)
document['_id'] = _document['_id'] # if _document :
document['_rev']= _document['_rev'] # document['_id'] = _document['_id']
# document['_rev']= _document['_rev']
self.dbase.save_doc(document) self.dbase.save_doc(document)
"""
write a given attribute to a document database
@param label scope of the row repair|broken|fixed|stats
@param row row to be written
"""
def write(self,**params): def write(self,**params):
"""
write a given attribute to a document database
@param label scope of the row repair|broken|fixed|stats
@param row row to be written
"""
document = self.dbase.get(self.uid) document = self.dbase.get(self.uid)
label = params['label'] label = params['label']
row = params['row']
if label not in document :
document[label] = [] if 'row' in params :
document[label].append(row) row = params['row']
row_is_list = isinstance(row,list)
if label not in document :
document[label] = row if row_is_list else [row]
elif isinstance(document[label][0],list) :
document[label].append(row)
else:
document[label] += row
else :
if label not in document :
document[label] = {}
if isinstance(params['data'],list) == False :
document[label] = dict(document[label],**params['data'])
else:
document[label] = params['data']
# if label not in document :
# document[label] = [] if isinstance(row,list) else {}
# if isinstance(document[label],list):
# document[label].append(row)
# else :
# document[label] = dict(document[label],**row)
self.dbase.save_doc(document) self.dbase.save_doc(document)
def flush(self,**params) : def flush(self,**params) :
size = params['size'] size = params['size'] if 'size' in params else 0
has_changed = False has_changed = False
document = self.dbase.get(self.uid) document = self.dbase.get(self.uid)
for key in document: for key in document:
@ -599,7 +634,7 @@ class CouchdbWriter(Couchdb,Writer):
content = document[key] content = document[key]
else: else:
continue continue
if isinstance(content,list): if isinstance(content,list) and size > 0:
index = len(content) - size index = len(content) - size
content = content[index:] content = content[index:]
document[key] = content document[key] = content
@ -607,8 +642,8 @@ class CouchdbWriter(Couchdb,Writer):
else: else:
document[key] = {} document[key] = {}
has_changed = True has_changed = True
if has_changed:
self.dbase.save_doc(document) self.dbase.save_doc(document)
def archive(self,params=None): def archive(self,params=None):
document = self.dbase.get(self.uid) document = self.dbase.get(self.uid)

Loading…
Cancel
Save