You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			281 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			281 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
	This file handles customer & plans associated with a given product/app
 | 
						|
	The subscription works as follows:
 | 
						|
	- 
 | 
						|
"""
 | 
						|
from __future__ import division
 | 
						|
from flask import Flask, request, session, render_template,Response
 | 
						|
import Domain
 | 
						|
from couchdbkit import Server, Document
 | 
						|
import stripe
 | 
						|
import json
 | 
						|
from StringIO import StringIO
 | 
						|
import re
 | 
						|
import os
 | 
						|
from utils.params import PARAMS
 | 
						|
from utils.transport import Couchdb, CouchdbReader
 | 
						|
PORT = 8100 if 'port' not in PARAMS else int(PARAMS['port']) ;
 | 
						|
path = PARAMS['path'] #os.environ['CONFIG']
 | 
						|
f = open(path)
 | 
						|
CONFIG = json.loads(f.read())
 | 
						|
stripe_keys = {
 | 
						|
    'secret_key': CONFIG['stripe']['secret'].strip(),
 | 
						|
    'publishable_key': CONFIG['stripe']['pub'].strip()
 | 
						|
}
 | 
						|
 | 
						|
stripe.api_key = stripe_keys['secret_key'].strip()
 | 
						|
 | 
						|
 | 
						|
	
 | 
						|
app = Flask(__name__)
 | 
						|
"""
 | 
						|
	This function will attempt to create an account for a user if the user does NOT exist
 | 
						|
	Then Setup the stage for initialization of {signup for, user-account}
 | 
						|
"""
 | 
						|
COUCHDB = Server(uri=CONFIG['couchdb']['uri']) ;
 | 
						|
 | 
						|
"""
 | 
						|
	This function subscribes a user to a given service for an application
 | 
						|
	This function guarantees not to duplicate subscriptions
 | 
						|
	@resource 	name	name of the application {cloud-music}
 | 
						|
	@header		key	service/plan
 | 
						|
"""
 | 
						|
@app.route('/subscribe/<app_name>',methods=['POST'])
 | 
						|
def subscribe(app_name):
 | 
						|
	#
 | 
						|
	# The name is the full name of the service
 | 
						|
	#
 | 
						|
 | 
						|
	key = request.headers['key']
 | 
						|
	uid = request.headers['uid']
 | 
						|
	plans = stripe.Plan.list().data	
 | 
						|
	plan = [item for item in plans if item.id == key]
 | 
						|
	resp = "0"
 | 
						|
	if plan :
 | 
						|
		couch_handler = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid)
 | 
						|
		DB = couch_handler.dbase
 | 
						|
		handler = Domain.User(DB,stripe) ;
 | 
						|
		handler.init(uid,plan)
 | 
						|
		resp = plan[0].id
 | 
						|
	return resp
 | 
						|
"""
 | 
						|
	This function returns the meta data about a given plan or set of plans for a given application
 | 
						|
	@resource app_name	application identifier
 | 
						|
	@header pid 		plan identifier (optional)
 | 
						|
	@header uid		user identifier
 | 
						|
"""
 | 
						|
@app.route('/get/info/<app_name>',methods=['GET'])
 | 
						|
def get_plan_info(app_name) :
 | 
						|
	uid = request.headers['uid']
 | 
						|
	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  ]
 | 
						|
	if pid is not None :
 | 
						|
		plans = [item['metadata'] for item in plans if item['id'] == pid]
 | 
						|
	return json.dumps(plans)
 | 
						|
@app.route('/get/sub/<app_name>')
 | 
						|
def get_sub_info(app_name):
 | 
						|
	uid = request.headers['uid']
 | 
						|
	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']
 | 
						|
	#
 | 
						|
	# @TODO: Return critical information only i.e:
 | 
						|
	#	- subscription state (dates,status)
 | 
						|
	#	- how much is owed
 | 
						|
	#	- subscription id
 | 
						|
	#
 | 
						|
	if pid is not None:
 | 
						|
		subs = [ sub for sub in lsub if sub['plan']['id'] == pid  ]
 | 
						|
	else:
 | 
						|
		subs = lsub
 | 
						|
	return json.dumps(subs)
 | 
						|
 | 
						|
@app.route('/subscribe/<name>',methods=['DELETE'])
 | 
						|
def cancel_subscribe(name) :
 | 
						|
	pass
 | 
						|
"""
 | 
						|
	This function defines if a given user is a customer or not
 | 
						|
	We should be able to tell by how we create customers
 | 
						|
"""
 | 
						|
@app.route('/checkout/<app_name>',methods=['GET'])
 | 
						|
def is_customer (app_name):
 | 
						|
	uid = request.args.get('uid')
 | 
						|
	pid = request.args.get('pid')
 | 
						|
	couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False)
 | 
						|
	info = couchdb.read()
 | 
						|
	lsub = info['subscriptions']
 | 
						|
	
 | 
						|
	plans = [dict(dict(item['plan'],**{"status":item['status']}),**{"subscription":item['id']}) for item in lsub]
 | 
						|
	if pid is not None:
 | 
						|
		plans = [item for item in plans if item['id'] == pid]
 | 
						|
	#
 | 
						|
	# Caching the subscription identifiers so we can create an invoice later on (if need be)
 | 
						|
	# @TODO Improve this process later on by allowing user's to pay for what they can (not everything)
 | 
						|
	#
 | 
						|
	session['plans'] = plans
 | 
						|
		
 | 
						|
 | 
						|
	# bill = {"amount":0,"count":0}
 | 
						|
	# key = ""
 | 
						|
	# if 'user-plans' in session :
 | 
						|
	# 	plans = session['user-plans'] ;
 | 
						|
	# 	amount = [plan['amount'] for plan in plans if plan['amount'] > 0]
 | 
						|
	# 	if len(amount) > 0:
 | 
						|
	# 		key = CONFIG['stripe']['pub'].strip()
 | 
						|
	# 		bill['count'] = len(amount)
 | 
						|
	# 		bill['amount']= sum(amount)
 | 
						|
	html = """
 | 
						|
	<form action="/pay" method="POST">
 | 
						|
		<script
 | 
						|
		src="https://checkout.stripe.com/checkout.js" class="stripe-button"
 | 
						|
		data-key=":key"
 | 
						|
		data-email=":email"
 | 
						|
		data-amount=":amount"
 | 
						|
		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>
 | 
						|
	"""
 | 
						|
	session['plans']
 | 
						|
	amount = sum([item['amount'] for item in plans])	
 | 
						|
	apikey = CONFIG['stripe']['pub'].strip()
 | 
						|
	html = html.replace(":email",uid).replace(":amount",str(amount)).replace(":key",apikey)
 | 
						|
	amount = amount / 100
 | 
						|
	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
 | 
						|
"""
 | 
						|
@app.route('/pay',methods=['POST'])
 | 
						|
def pay():
 | 
						|
	
 | 
						|
	token 		= request.form['stripeToken']
 | 
						|
	uid		= request.form['stripeEmail']
 | 
						|
	tokenType	= request.form['stripeTokenType']
 | 
						|
	couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False)
 | 
						|
	DB = couchdb.dbase #COUCHDB.get_db(CONFIG['couchdb']['db']) ;
 | 
						|
	handler = Domain.User(DB,stripe) ;
 | 
						|
	plans = session['plans']
 | 
						|
	# Assuming all is fine, we must do the following at this point
 | 
						|
	#	- create an invoice with the designated subscriptions
 | 
						|
	#	- create a charge on the invoice
 | 
						|
	#
 | 
						|
	# Let's insure the preconditions are met i.e
 | 
						|
	#	card,invoice
 | 
						|
	info	= session['user-info']
 | 
						|
	uid	= info['_id']
 | 
						|
	if 'sources' not in info or token not in info['sources']:
 | 
						|
		#
 | 
						|
		# Let's create the card
 | 
						|
		handler.save_card(uid,token)
 | 
						|
	#
 | 
						|
	# Let's create a charge here ...
 | 
						|
	plans = session['user-plans']
 | 
						|
	
 | 
						|
	amount=[item['price'] for item in plans if item['status'] == 'past_due' ]
 | 
						|
	if len(amount) == 0:
 | 
						|
		amount = 0
 | 
						|
	else:
 | 
						|
		amount = sum(amount)
 | 
						|
	handler.charge(uid,amount)
 | 
						|
	session['user-info'] = handler.user
 | 
						|
	return ('',204)
 | 
						|
	#return render_template("bill.html",plans=plans, total_amount=amount)
 | 
						|
 | 
						|
 | 
						|
@app.route('/bill',methods=['GET'])
 | 
						|
def bill():
 | 
						|
	return render_template('bill.html')
 | 
						|
@app.route('/buy',methods=['POST'])
 | 
						|
def buy():
 | 
						|
	id = request.form['id'] ;
 | 
						|
	
 | 
						|
	if id in ['subscribe','now']:
 | 
						|
		email = request.form['stripeEmail'] 
 | 
						|
		token = request.form['stripeToken']
 | 
						|
		user = Domain.User(DB,stripe,token) ;
 | 
						|
		user.init(email) ;
 | 
						|
		if user.exists() :
 | 
						|
			print "Update with anything if need be"
 | 
						|
			pass
 | 
						|
		else:
 | 
						|
			#
 | 
						|
			# Create the user with the associated plan/or payment method
 | 
						|
			#
 | 
						|
			user.save() ;
 | 
						|
			user.publish()
 | 
						|
	else:
 | 
						|
		pass
 | 
						|
	return "0"
 | 
						|
"""
 | 
						|
	This function provides general information about service plans
 | 
						|
	@TODO: provide filtering criteria
 | 
						|
"""
 | 
						|
@app.route('/plans',methods=['GET'])
 | 
						|
def plans():
 | 
						|
	plans = stripe.Plan.list().data	
 | 
						|
	if 'filter' in request.headers:
 | 
						|
		filter = request.headers['filter']
 | 
						|
		
 | 
						|
		plans = [ item for item in plans if re.match(filter,item.name)]
 | 
						|
	else:
 | 
						|
		#
 | 
						|
		# Let's get a user's subscription information
 | 
						|
		#
 | 
						|
		uid = request.headers['uid']
 | 
						|
		
 | 
						|
	if 'uid' in request.headers and request.headers['uid'] != '':
 | 
						|
		uid = request.headers['uid']
 | 
						|
		DB = COUCHDB.get_db(CONFIG['couchdb']['db']) ;
 | 
						|
		handler = Domain.User(DB)
 | 
						|
		handler.init(uid) 
 | 
						|
		myplans = [{"id":item.plan.id,"price":item.plan.amount/100,"feature":item.plan.metadata['info'],"status":item.status} for item in handler.subscriptions()]
 | 
						|
		keys = [plan['id'] for plan in myplans]
 | 
						|
		plans = [plan for plan in plans if plan['price'] > 0 and plan['id'] not in keys]
 | 
						|
		plans = {'myplans':myplans,"plans":plans}
 | 
						|
		session['user-plans'] = myplans
 | 
						|
	plans = json.dumps(plans)
 | 
						|
	return plans		
 | 
						|
"""
 | 
						|
	This function subscribes a user to a given plan(s)
 | 
						|
	If the plan is new, then we do NOT need a credit card info
 | 
						|
 | 
						|
	@header	app	application/requesting service
 | 
						|
	@body	info	{user:_id,plan:[]}
 | 
						|
"""
 | 
						|
@app.route('/_subscribe',methods=['POST'])
 | 
						|
def _subscribe():
 | 
						|
	
 | 
						|
	
 | 
						|
	
 | 
						|
	
 | 
						|
	if 'user-info' not in session:
 | 
						|
		info = request.get_json(silent=True)
 | 
						|
		user = info['user']
 | 
						|
		plans = info['plans']
 | 
						|
	else:
 | 
						|
		plans = [{"id":id} for id in  request.get_json(silent=True)]
 | 
						|
		user = session['user-info']
 | 
						|
	app = request.headers['app']
 | 
						|
	#
 | 
						|
	# @TODO:
 | 
						|
	# This should be handled by transport layer ...
 | 
						|
	#
 | 
						|
	DB = COUCHDB.get_db(CONFIG['couchdb']['db']) ;
 | 
						|
	handler = Domain.User(DB,stripe)
 | 
						|
	r = handler.subscribe(user['id'],plans)
 | 
						|
	return json.dumps(r)
 | 
						|
 | 
						|
if __name__ == '__main__' :
 | 
						|
	app.debug = True ;
 | 
						|
	app.secret_key = '360-8y-[0v@t10n]+kr81v17y'
 | 
						|
	app.run(port=PORT,threaded=True)
 |