""" Steve L. Nyemba The Phi Technology, LLC - Store This file handles customer & plans associated with a given product/app We understand that a product will have multiple plans under it and we make sure that we always have a free version: - 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. - The store must be prefixed with an authentication module, because customers must be signed in before making a purchase. """ from __future__ import division from flask import Flask, request, session, render_template, Response,redirect from flask_session import Session from flask_cors import CORS # from flask_pymongo import PyMongo # from flask_mongo_sessions import MongoDBSessionInterface # # from flask.ext.mongoengine import MongoEngine import transport import stripe import json # from StringIO import StringIO import re import os from utils.params import PARAMS from transport import factory # from store import Store import store import transport from datetime import datetime import numpy as np import requests import os # 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()) CONTEXT= PARAMS['context'] if 'context' in PARAMS else '' stripe_keys = { 'secret_key': CONFIG['stripe']['secret'].strip(), 'publishable_key': CONFIG['stripe']['pub'].strip() } stripe.api_key = stripe_keys['secret_key'].strip() if 'CLOUDVIEW_CONTEXT' in os.environ and os.environ['CLOUDVIEW_CONTEXT'] not in CONFIG['cloud-view']: context = os.environ['CLOUDVIEW_CONTEXT'] CONFIG['cloud-view'] = CONFIG['cloud-view'].replace('cloud-view',context) app = Flask(__name__) CORS(app) SYS_STORE = CONFIG['store'] LOGGER = transport.factory.instance(**SYS_STORE['logger']) # @app.route("/donate") # def _donate() : # amount = float(request.args['amount']) if 'amount' in request.args else 5.0 # args = {'amount':amount,'context':CONTEXT} # return render_template('donate.html',**args) def get_id(id=None): return CONFIG['default']['id'] if not id else id @app.route("/") def index(): if 'default' in CONFIG: id = CONFIG['default']['id'] return get_plans_ui(id) headers = {"content-type":"application/json"} mystore = store.factory.instance(name=CONFIG['default']['id']) products = mystore.get.products() args = {"context":CONTEXT,"products":products} return json.dumps(products),headers @app.route("/terms") def terms(): _args = CONFIG['about'] _args['context']= CONTEXT _args['year'] = datetime.now().year return render_template("terms.html",**_args) # return render_template("index.html",**args) @app.route("/json/") def get_plans_json(id): """ This function returns the plans for a given product """ 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("/store",methods=['POST','PUT']) def to_session(): """ This function will store/persist session variables for reuse """ info = request.json if info : for key in info : session[key] = info[key] return "{}",200 @app.route("/signup",methods=['GET','POST']) def signup(): """ This function will signup a user to a plan or upgrade them if necessary The assumption here is one product - one plan :request.form stripeToken (optional) """ if request.method == 'GET' : if 'auth' not in session : return "0",403 _object = {"data-key":CONFIG['stripe']['pub'].strip()} return json.dumps(_object),{"Content-Type":"application/json"} else: # # We are receiving a call from stripe framework and we need create a charge for this # @NOTE: https://stripe.com/docs/payments/accept-a-payment-charges#web-create-charge token = request.form['stripeToken'] if 'stripeToken' in request.form else None email = session['auth']['user']['uid'] plan_id = session['plan']['id'] id = session['product']['name'] mystore = store.factory.instance(name=id,email=email) user = mystore.user plans = user.plan.info() plans = [plans] if isinstance(plans,dict) else plans has_plan = [1 for item in plans if item['id'] == plan_id] if np.sum(has_plan) == 0 and len(plans) == 0: mystore.subscribe(id=plan_id,email=email) elif np.sum(has_plan)==0 and len(plans) > 0: # # We should upgrade/downgrade the current plan # mystore.plan.upgrade(plan_id,email) uri = session['product']['metadata']['uri'] if 'uri' in session['product']['metadata'] else '#status' # # @NOTE: # This function assumes one product one plan, operations outside of this scope will require a new function return """ """.replace(":uri",uri) # @app.route("/ui/signup/",methods=['GET','PUT']) @app.route("//signup",methods=['GET','PUT']) def signup_form(id): """ This function loads the signup form with cloud-view authentication support This allows us to know who is signing up or upgrading their plan """ 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' args['cloud-view'] = CONFIG['cloud-view'] return render_template("signup.html",**args) @app.route("/me") def me (): # #{'user': {'uid': 'nyemba@gmail.com', 'uii': 'Steve Nyemba', 'usage': {'used': 1.012175691, 'size': 4.02653184, 'units': 'GB'}, 'sid': 'dropbox'}, 'key': 'nsfCKKUWunUAAAAAAAAAAUMzU6oR6qp9TK7t-_zop-sBOcEaM3eb1mRi_PG8bM69', 'features': {}} # mystore = store.factory.instance(name = session['product']['name']) # mystore.init(email=session['auth']['user']['uid']) try: if 'auth' in session and 'product' in session : email = session['auth']['user']['uid'] name = session['product']['name'] mystore = store.factory.instance(name=name,email=email) payments = mystore.user.invoices() cards = mystore.user.card.html() if mystore.user.card.exists() else None _args = {'context':CONTEXT,"user":session['auth']['user'],"product":session['product'],"plan":session['plan'],"payments":payments,"cards":cards} return render_template('me.html',**_args) except Exception as e: print (e) pass return render_template("none.html",context=CONTEXT) # return redirect("http://healthcareio.the-phi.com") @app.route("/me/card",methods=['POST']) def me_card (): if 'auth' in session : email = session['auth']['user']['uid'] name = session['product']['name'] mystore = store.factory.instance(name=name,email = email) body = request.json try: mystore.user.card.add(card=body) except Exception as e: print (e) return "0",200 return "1",200 # pricing = mystore.get.checkout() #-- pricing # pricing = [{"unit_amount":500,"currency":"usd"},{"unit_amount":1000,"currency":"usd"},{"unit_amount":2500,"currency":"usd"}] # args = {"product":session['product']} else: return "0",403 return render_template("card.html") @app.route("//cancel",methods=['POST']) def cancel(id): # # This function will cancel the user's current plan STATUS = "1" try: if 'auth' in session : email = session['auth']['user']['uid'] mystore = store.factory.instance(name=id,email=email) _id = mystore.user.plan.sub_id() mystore.cancel(_id) session['plan'] = None session['payments'] = mystore.user.invoices() else: STATUS = "0" except Exception as e: print (e) STATUS = "0" pass return STATUS,200 # @app.route("/ui/") @app.route("//plans") def get_plans_ui(id): """ This function loads the form for signup and information about the various plans for a given product. :id name of the products """ id = get_id(id) #-- will return default if need be 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['product'] = mystore.product args['theme'] = 'theme-clouds' args['cloud_view'] = CONFIG['cloud-view'].replace(':id',id) args['product'] = mystore.product return render_template('plans.html',**args) @app.route("/goto/",methods=['POST','GET']) def redirect(id ) : """ This function will redirect to a given product page given a payload. The payload is intended to set the a session :info """ mystore = store.factory.instance(name=id) args = {"context":CONTEXT,"product":mystore.product} HEADER = {"Location":mystore.product['metadata']['uri']} info = request.json if info : session['auth'] = info['auth'] session['plan'] = info['plan'] session['product'] = mystore.product return "1",HEADER else: return "0",403 pass @app.route("/donate") def _donate(): _id = get_id() return donate(_id) @app.route("//donate") def donate (id) : """ This will charge a given user/card for a given product. i.e it is designed for a one-time payment """ mystore = store.factory.instance(name=id) # pricing = mystore.get.checkout() #-- pricing pricing = [{"unit_amount":500,"currency":"usd"},{"unit_amount":1000,"currency":"usd"},{"unit_amount":2500,"currency":"usd"}] args = {"product":mystore.product,"plans":mystore.get.plans(),"context":CONTEXT,"pricing":pricing} return render_template("card.html",**args) @app.route("//pay") def pay(id): email = session['auth']['user']['uid'] fname = session['auth']['user']['uii'].split(' ')[0] if 'uii' in session['auth']['user'] else '' lname = " ".join(session['auth']['user']['uii'].split(' ')[1:]) if 'uii' in session['auth']['user'] else '' mystore = store.factory.instance(name=id,email=email) #-- amount = session['plan']['amount'] # cards = mystore.user.card.html() if mystore.user.card.exists() else None _args = {"product":mystore.product,"pricing":None,"context":CONTEXT,"email":email,"lname":lname,"fname":fname,"amount":amount} #,"cards":cards} return render_template("card.html",**_args) @app.route("/html/dialog",methods=['POST']) def get_dialog(): info = request.get_json() _args = {"context":CONTEXT} _args = dict(_args,**info) return render_template('dialog.html',**_args) @app.route('//pay',methods=['POST','PUT']) def _payme(id) : info = request.get_json() mystore = store.factory.instance(name=id,email=info['email']) user = mystore.user # # ... Let us massage the data so it can go through properly _args = {} if 'has_card' not in info and not info['has_card'] : card = {"number":info['number'],"cvc":info['cvc']} card['exp_month'] = info['exp'].split("/")[0] card['exp_year'] = info['exp'].split("/")[1] card['name'] = " ".join([info["fname"],info["lname"]]) _args = {"email":info['email'],"amount":info['amount'],"currency":"usd"} else: # # Let us make a payment using the source we already have card = {} _args['card'] = card # # if a plan information is provided we are running the card for the plan ? # if 'plan' in session : # # We are dealing with a subscription here and we should run this through the subscription engine # and NOT charge the user immediately (handled by stripe) # # _args['amount'] = info['plan']['amount'] plan = session['plan'] _args['metadata'] = {"payment":plan['nickname'],"product":id} _args['plan'] = plan # # @TODO: If we have a card we must add it to the cards on file # if card : user.card.add(card=card) if user.plan.info() : r = mystore.plan.upgrade(plan['id'],info['email']) else: r = mystore.plan.subscribe(id=plan['id'],email=info['email']) pass else: # # For now we consider this a donation or other payment _args['metadata'] = {"payment":"charge","product":id} _args['statement_descriptor'] = " ".join([str(info['amount']),id]) r = user.charge(**_args) if not r : msg,status = "Payment error enountered",403 else : msg,status = "Payment accepted, Thank you", 200 return Response(msg,status=status) @app.route("/me/logout",methods=["POST","GET"]) def logout(): session.clear() return "1",200 @app.route("/init/",methods=['POST']) def init(product): """ 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 @param uid user's email (primary) @param pid plan identifier """ # # get product and plans # if 'uid' in request.headers : uid = request.headers['uid'] plan_id = request.headers['pid'] if 'pid' in request.headers else None else: _info = request.json() mystore = store.factory.instance(name=product,email=uid) user = mystore.user user.init (uid) # prrint (user.plan.info()) if not user.plan.info() : mystore.plan.subscribe(email = user.info()['email']) features = user.plan.info()['metadata']['features'] if 'features' in user.plan.info()['metadata'] else {} # #@TODO: Log that uid, has initiated use of product # HEADERS ={"content-type":"application/json"} return features,HEADERS @app.route("//init",methods=['POST','PUT']) def _init(id): return init(id) def get_plans(product) : lproducts = stripe.Product.list() 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 [] # @app.route('/features/') @app.route("/features") def _features(): _id = get_id() return features(_id) @app.route('//features') def features(id): """ 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 """ plans = get_plans(id) return json.dumps(plans),{"content-type":"application/json"} if __name__ == '__main__' : # # setup mongodb session management (not sure why) # app.config['SESSION_TYPE'] = 'filesystem' # app.config['MONGO_URI'] = 'mongodb://localhost:27017' # app.config['SESSION_MONGODB_DB'] = 'sessions' #CONFIG['d']['logger']['db'] # app.config['SESSION_MONGODB_COLLECT'] = CONFIG['default']['id'] # mongo = PyMongo(app) # app.session_interface = MongoDBSessionInterface(app,mongo.db, 'store') # app.config['MONGODB_DB'] = 'sessions' # app.session_interface = MongoDBSessionInterface(app, mongo.db, 'store') app.debug = True ; app.secret_key = '360-8y-[0v@t10n]+kr81v17y' app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024 Session(app) app.run(port=PORT,threaded=True,host='0.0.0.0') # app.run() #'0.0.0.0',PORT,True,threaded=True)