Subscription handling, with user interface

@TODO: Refactor endpoint and flow/sessions and user/email handling
legacy
Steve L. Nyemba 8 years ago
parent c3ae16aef8
commit 73bb8b63d9

@ -12,6 +12,7 @@ class User:
self.stripe = stripe; self.stripe = stripe;
self.stripeToken= stripeToken self.stripeToken= stripeToken
self.user = None self.user = None
""" """
This function will cast an object to JSON, This function will cast an object to JSON,
It is designed to address an inherent limitation of stripe object hierarchy It is designed to address an inherent limitation of stripe object hierarchy
@ -80,7 +81,6 @@ class User:
# #
if self.user : if self.user :
r = stripe.Customer.retrieve(self.user['id']) r = stripe.Customer.retrieve(self.user['id'])
return r.subscriptions return r.subscriptions
else: else:
return [] return []
@ -97,6 +97,10 @@ class User:
customer=id, customer=id,
plan=plan['id'] plan=plan['id']
) ; ) ;
#
# We need to create an invoiced item out of this plan
# We will attach it later to an invoice ...
#
r.append(x) r.append(x)
return r return r
""" """
@ -118,6 +122,11 @@ class User:
self.db.save_doc(user) ; self.db.save_doc(user) ;
self.user = user self.user = user
def invoice(self,uid,plans,iid):
self.user = self.db.get(uid)
id = self.user['id']
r = [ stripe.InvoiceItem.create(customer=id,amount=item['amount'],currency=item['currency'],description=item['statement_descriptor'],subscription=item['subscription'],invoice=iid) for item in plans]
return r
""" """
This function creates an invoice This function creates an invoice
@pre : an item with amount > 0 and status past_due @pre : an item with amount > 0 and status past_due
@ -125,26 +134,28 @@ class User:
def get_invoices(self,uid): def get_invoices(self,uid):
info = self.db.get(uid) info = self.db.get(uid)
id = info['id'] id = info['id']
return stripe.Invoice.list(customer=id) #return stripe.Invoice.list(customer=id)
""" """
This function will clear a particular invoice (hopefully). No partial payments should be accepted This function will clear a particular invoice (hopefully). No partial payments should be accepted
@pre : card,invoice @pre : card,invoice
""" """
def charge(self,uid,amount): def charge(self,uid,token,plans):
info = self.db.get(uid) info = self.db.get(uid)
id = info['id'] id = info['id']
uid = info['_id'] sid = plans[0]['subscription']
invoices= self.get_invoices(uid) user = stripe.Customer.retrieve(id)
index = -1 user.source = token
for invoice in invoices : user.save()
if invoice.paid == False and invoice.amount_due == amount and amount > 0: for plan in plans:
index = info['invoices'].index(ii) sid = plan['subscription']
invoice.pay() if plan['amount'] > 0 :
del info['invoices'][index] print [' *** ',plan['amount']]
self.db.save_doc(info) _invoice= stripe.Invoice.create(customer=id,subscription=sid)
break r = _invoice.pay()
pass; print r
""" """
This function is designed to determine if the user exists or not This function is designed to determine if the user exists or not
We will check the couchdb and the stripe backend We will check the couchdb and the stripe backend

@ -66,15 +66,28 @@ def subscribe(app_name):
""" """
@app.route('/get/info/<app_name>',methods=['GET']) @app.route('/get/info/<app_name>',methods=['GET'])
def get_plan_info(app_name) : def get_plan_info(app_name) :
uid = request.headers['uid'] plans = []
pid = request.headers['pid'] if 'pid' in request.headers else None print ' *** ','uid' in request.headers
couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) if 'uid' in request.headers :
info = couchdb.read() uid = request.headers['uid']
lsub = info['subscriptions'] pid = request.headers['pid'] if 'pid' in request.headers else None
couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False)
info = couchdb.read()
lsub = info['subscriptions']
plans = [ sub['plan'] for sub in lsub if sub['ended_at'] is None ] plans = [ sub['plan'] for sub in lsub if sub['ended_at'] is None ]
if pid is not None : if pid is not None :
plans = [item['metadata'] for item in plans if item['id'] == pid] plans = [item['metadata'] for item in plans if item['id'] == pid]
else:
#
# This function returns the plans for a given application
# We assume the application name is the prefix of the plan identifier in stripe
#
plans = stripe.Plan.list(limit=10)
handler = Domain.User(None)
plans = handler.cast(plans.data)
plans = [item for item in plans if re.match(app_name,item['name'])]
return json.dumps(plans) return json.dumps(plans)
@app.route('/get/sub/<app_name>') @app.route('/get/sub/<app_name>')
def get_sub_info(app_name): def get_sub_info(app_name):
@ -94,6 +107,20 @@ def get_sub_info(app_name):
else: else:
subs = lsub subs = lsub
return json.dumps(subs) return json.dumps(subs)
@app.route('/get/plans/<app_name>')
def get_plans(app_name) :
apikey = CONFIG['stripe']['pub'].strip()
#
# This function returns the plans for a given application
# We assume the application name is the prefix of the plan identifier in stripe
#
uid = session['uid'] if 'uid' in session else 'nyemba@gmail.com'
plans = stripe.Plan.list(limit=10)
handler = Domain.User(None)
plans = handler.cast(plans.data)
plans = [item for item in plans if re.match(app_name,item['name'])]
return render_template('subscribe.html',uid=uid,app_name=app_name,plans=plans,apikey=apikey)
@app.route('/subscribe/<name>',methods=['DELETE']) @app.route('/subscribe/<name>',methods=['DELETE'])
def cancel_subscribe(name) : def cancel_subscribe(name) :
@ -130,7 +157,7 @@ def is_customer (app_name):
# bill['count'] = len(amount) # bill['count'] = len(amount)
# bill['amount']= sum(amount) # bill['amount']= sum(amount)
html = """ html = """
<form action="/pay" method="POST"> <form action="/pay/:app_name" method="POST">
<script <script
src="https://checkout.stripe.com/checkout.js" class="stripe-button" src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key=":key" data-key=":key"
@ -148,19 +175,20 @@ def is_customer (app_name):
session['plans'] session['plans']
amount = sum([item['amount'] for item in plans]) amount = sum([item['amount'] for item in plans])
apikey = CONFIG['stripe']['pub'].strip() apikey = CONFIG['stripe']['pub'].strip()
html = html.replace(":email",uid).replace(":amount",str(amount)).replace(":key",apikey) html = html.replace(":email",uid).replace(":amount",str(amount)).replace(":key",apikey).replace(":app_name",app_name)
amount = amount / 100 amount = amount / 100
return render_template('bill.html',apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount,html=html) return render_template('bill.html',apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount,html=html)
""" """
This function is intended to performa an actual payment This function is intended to performa an actual payment
""" """
@app.route('/pay',methods=['POST']) @app.route('/pay/<app_name>',methods=['POST'])
def pay(): def pay(app_name):
token = request.form['stripeToken'] token = request.form['stripeToken']
uid = request.form['stripeEmail'] uid = request.form['stripeEmail']
tokenType = request.form['stripeTokenType'] tokenType = request.form['stripeTokenType']
couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False)
DB = couchdb.dbase #COUCHDB.get_db(CONFIG['couchdb']['db']) ; DB = couchdb.dbase #COUCHDB.get_db(CONFIG['couchdb']['db']) ;
handler = Domain.User(DB,stripe) ; handler = Domain.User(DB,stripe) ;
plans = session['plans'] plans = session['plans']
@ -168,24 +196,27 @@ def pay():
# - create an invoice with the designated subscriptions # - create an invoice with the designated subscriptions
# - create a charge on the invoice # - create a charge on the invoice
# #
#items = handler.invoice(uid,plans)
handler.charge(uid,token,plans)
# Let's insure the preconditions are met i.e # Let's insure the preconditions are met i.e
# card,invoice # card,invoice
info = session['user-info'] #info = session['user-info']
uid = info['_id'] #uid = info['_id']
if 'sources' not in info or token not in info['sources']: #if 'sources' not in info or token not in info['sources']:
# #
# Let's create the card # Let's create the card
handler.save_card(uid,token) # handler.save_card(uid,token)
# #
# Let's create a charge here ... # Let's create a charge here ...
plans = session['user-plans'] #plans = session['user-plans']
amount=[item['price'] for item in plans if item['status'] == 'past_due' ] #amount=[item['price'] for item in plans if item['status'] == 'past_due' ]
if len(amount) == 0: #if len(amount) == 0:
amount = 0 # amount = 0
else: #else:
amount = sum(amount) # amount = sum(amount)
handler.charge(uid,amount) #handler.charge(uid,amount)
session['user-info'] = handler.user session['user-info'] = handler.user
return ('',204) return ('',204)
#return render_template("bill.html",plans=plans, total_amount=amount) #return render_template("bill.html",plans=plans, total_amount=amount)

@ -80,7 +80,7 @@
{% for item in plans %} {% for item in plans %}
<tr class="" style="font-size:11px"> <tr class="" style="font-size:11px">
<td> {{item.statement_descriptor}} </td> <td> {{item.statement_descriptor}} </td>
<td align="right"> {{item.amount/100}} </td> <td align="right"> {{item.amount/100}} </td>
<td style="text-transform:capitalize"> {{item.status}} </td> <td style="text-transform:capitalize"> {{item.status}} </td>
</tr> </tr>

@ -0,0 +1,134 @@
<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">
<style>
body{
font-family:sans-serif;
font-size:14px;
font-weight:lighter;
}
.caption {
font-size:28px; font-weight:lighter ;
font-family:sans-serif;
text-transform:capitalize;
}
.medium-caption {
font-size:18px;
font-weight:lighter;
font-family:sans-serif ;
text-transform:capitalize;
}
.border-bottom{ border-bottom:1px solid #CAD5E0}
.left { float:left}
.small { font-family:verdana; font-size:11px;}
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;
border:1px solid #CAD5E0
}
.action:hover { background-color:#4682b4; color:white}
tr {font-family:sans-serif; font-size:14px;}
td {padding:4px; font-weight:lighter; padding:4px;}
@media only screen and (min-device-width: 320px){
table {width:99%}
body {font-size:12px;}
tr {font-family:sans-serif; font-size:12px;}
td {padding:4px; font-weight:lighter}
}
@media only screen and (min-device-width: 768px){
body {font-size:12px}
table {width:60%; margin-left:20%}
}
</style>
<body>
<div>
</div>
<br>
<div id="grid">
<table align="center" style="border-color:transparent">
<tr>
<td style="width:50%">
<div align="left" class="default">
<img src="/static/img/logo-0.png" style="width:48px; margin:4px" class="left" style="margin:4px">
<div style="padding:8px">The Phi Technology
<div class="small" style="text-transform:capitalize">{{ app_name }}</div>
</div>
</div>
</td>
<td class="small" valign="middle">
<div>Call : 615-866-0454</div>
<div>Email: support@the-phi.com</div>
</td>
</tr>
</table>
<table align="center" class="border-top" style="background-image:url(https://az616578.vo.msecnd.net/files/responsive/cover/main/desktop/2016/05/09/635983505329496433385654456_concert-audience.jpg)">
<tr >
<td colspan="{{ plans|length}}" align="center" class="border-bottom">
<div class="caption">{{app_name.replace('-',' ') }}</div>
<div class="small">Available Plans, Charges will apply after trial period</div>
</td>
</tr>
<tr class="default">
{% for item in plans%}
{% if loop.index0 > 0 %}
<td align="center" style="border-left:1px solid #CAD5E0">
{% else %}
<td align="center">
{% endif %}
<div class="medium-caption">{{ item.name.replace('-',' ') }}</div>
<div>{{ item.metadata.info}}</div>
{% if item.amount > 0%}
<div class="caption">{{ item.amount/100 }}</div>
<div class="small"> {{item.currency.upper() }}/{{ item.interval }}
{% if item.trial_period_days %}
&amp; {{ item.trial_period_days }} days of trial
{% endif %}
</div>
<br>
<div>
<form action="/pay/{{app_name|safe}}" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{apikey|safe}}"
data-email="{{uid|safe}}"
data-amount={{item.amount|safe}}
data-name="The Phi Technology LLC"
data-description=""
data-image="https://s3.amazonaws.com/stripe-uploads/acct_15kiA1EUWsmgY81Amerchant-icon-1443577505909-the-phi-logo.png"
data-locale="auto">
</script>
</form>
</div>
{% else %}
<div class="caption">Free</div>
{% endif %}
</td>
{% endfor %}
</tr>
</table>
<br>
</div>
</body>
Loading…
Cancel
Save