From 37845eb43b1984986616159d8b0260cb6c133a76 Mon Sep 17 00:00:00 2001 From: Steve Nyemba Date: Fri, 11 Sep 2020 14:07:13 -0500 Subject: [PATCH] bug fixes with payments and donations --- src/api/index.py | 321 ++++++++++++++++++++++------------ src/api/static/js/payment.js | 80 +++++++-- src/api/store.py | 54 ++++-- src/api/templates/card.html | 90 +++++++--- src/api/templates/dialog.html | 3 +- src/api/templates/plans.html | 178 ++++++++++--------- src/api/templates/signup.html | 49 +++--- src/init.sh | 2 +- 8 files changed, 515 insertions(+), 262 deletions(-) diff --git a/src/api/index.py b/src/api/index.py index 2e79a08..c58b56c 100755 --- a/src/api/index.py +++ b/src/api/index.py @@ -10,7 +10,7 @@ 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 +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 @@ -54,12 +54,12 @@ CORS(app) SYS_STORE = CONFIG['store'] -@app.route("/donate") -def donate() : - print (request.args['amount']) - amount = float(request.args['amount']) if 'amount' in request.args else 5.0 - args = {'amount':amount,'context':CONTEXT} - return render_template('donate.html',**args) +# @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) @app.route("/") def index(): mystore = store.factory.instance(name='music') @@ -91,7 +91,7 @@ def to_session(): if info : for key in info : session[key] = info[key] - return "{}" + return "{}",200 @app.route("/signup",methods=['GET','POST']) def signup(): """ @@ -137,7 +137,8 @@ def signup(): """.replace(":uri",uri) -@app.route("/ui/signup/",methods=['GET','PUT']) +# @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 @@ -154,7 +155,71 @@ def signup_form(id): args['cloud-view'] = CONFIG['cloud-view'] return render_template("signup.html",**args) -@app.route("/ui/") +@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: + 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: + pass + return redirect("https://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. @@ -167,9 +232,10 @@ def get_plans_ui(id): 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' # print (mystore.product.keys()) - session['cloud-view'] = CONFIG['cloud-view'] + session['cloud-view'] = CONFIG['cloud-view'].replace(':id',id) session['product'] = mystore.product return render_template('plans.html',**args) @app.route("/goto/",methods=['POST','GET']) @@ -193,7 +259,7 @@ def redirect(id ) : return "0",403 pass @app.route("//donate") -def payme (id) : +def donate (id) : """ This will charge a given user/card for a given product. i.e it is designed for a one-time payment """ @@ -203,7 +269,18 @@ def payme (id) : args = {"product":mystore.product,"plans":mystore.get.plans(),"context":CONTEXT,"pricing":pricing} return render_template("card.html",**args) -@app.route("/ui/dialog",methods=['POST']) +@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() @@ -213,33 +290,61 @@ def get_dialog(): @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 - 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 = {"card":card,"email":info['email'],"amount":info['amount'],"currency":"usd"} + _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 info : + 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 + 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 - _args['metadata'] = {"payment":"donation","product":id} - _args['description'] = " ".join([id,_args['metadata']['payment']]) - r = user.charge(**_args) + # 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 = "Problem enountered with card",403 + 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): @@ -337,7 +442,8 @@ def get_plans(product) : else: return [] -@app.route('/features/') +# @app.route('/features/') +@app.route('//features') def features(product): """ This function returns the plan/features of a user for a given application if provided a uid (email) @@ -361,96 +467,97 @@ def features(product): # # formatting plans for the output # - return json.dumps(plans) -""" - This function returns a user's plans/status for an application - @header uid user's email address -""" -@app.route('/status/') -def status(app_name): + return json.dumps(plans),{"content-type":"application/json"} +# """ +# This function returns a user's plans/status for an application +# @header uid user's email address +# """ +# @app.route('/status/') +# def status(app_name): - uid = request.headers['uid'] +# uid = request.headers['uid'] - couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) - handler = Domain.User(couchdb.dbase,stripe) - handler.initialize(uid) - #lsub = handler.subscriptions() - plans = handler.plans() - return json.dumps(plans) -""" - This endpoint is the signup form for a given application, - It will allow user's to be able to signup for various plans - @pre 'uid' in request.headers -""" -@app.route('/signup/') -def _signup(product) : - apikey = CONFIG['stripe']['pub'].strip() +# couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) +# handler = Domain.User(couchdb.dbase,stripe) +# handler.initialize(uid) +# #lsub = handler.subscriptions() +# plans = handler.plans() +# return json.dumps(plans) +# """ +# This endpoint is the signup form for a given application, +# It will allow user's to be able to signup for various plans +# @pre 'uid' in request.headers +# """ +# @app.route('/signup/') +# @app.route('//signup') +# def _signup(product) : +# 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 - # - if 'user-info' in session : - _info = session['user-info'] - session['uid'] = _info['uid'] - uid = session['uid'] if 'uid' in session else '' - can_purchase = True - if uid is None : - uid = request.args.get('uid') if 'uid' in request.args else None - plans = get_plans(product) +# # +# # This function returns the plans for a given application +# # We assume the application name is the prefix of the plan identifier in stripe +# # +# if 'user-info' in session : +# _info = session['user-info'] +# session['uid'] = _info['uid'] +# uid = session['uid'] if 'uid' in session else '' +# can_purchase = True +# if uid is None : +# uid = request.args.get('uid') if 'uid' in request.args else None +# plans = get_plans(product) - # - # @TODO: Mark the plans the current user is signed up for - # - if 'themes' in CONFIG: - theme = CONFIG['themes'][product] if product in CONFIG['themes'] else CONFIG['theme']['default'] - else: - theme = '' - platform='web' if 'platform' not in request.args else request.args['platform'] - alias = plans[0]['product_alias'] - if 'user-plan' in session : - user_plans = [item['id'] for item in session['user-plan']] - else : - user_plans = [] - # active_plan = session['active-plans'] if 'active-plans' in session else [] - args = {"context":CONTEXT,"theme":theme,"uid":uid,"alias":alias,"platform":platform,"app_name":product,"apikey":apikey,"plans":plans} - args['active_plans'] = user_plans +# # +# # @TODO: Mark the plans the current user is signed up for +# # +# if 'themes' in CONFIG: +# theme = CONFIG['themes'][product] if product in CONFIG['themes'] else CONFIG['theme']['default'] +# else: +# theme = '' +# platform='web' if 'platform' not in request.args else request.args['platform'] +# alias = plans[0]['product_alias'] +# if 'user-plan' in session : +# user_plans = [item['id'] for item in session['user-plan']] +# else : +# user_plans = [] +# # active_plan = session['active-plans'] if 'active-plans' in session else [] +# args = {"context":CONTEXT,"theme":theme,"uid":uid,"alias":alias,"platform":platform,"app_name":product,"apikey":apikey,"plans":plans} +# args['active_plans'] = user_plans - return render_template('subscribe.html',**args) #context=CONTEXT,uid=uid,alias=alias,platform=platform,app_name=product,plans=plans,apikey=apikey) +# return render_template('subscribe.html',**args) #context=CONTEXT,uid=uid,alias=alias,platform=platform,app_name=product,plans=plans,apikey=apikey) -@app.route('/subscribe/',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/',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) - r = couchdb.view('federation/uid_map',key=uid) - id = r[0]['value'] - couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=id,create=False) - info = couchdb.read() - lsub = info['subscriptions'] +# @app.route('/subscribe/',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/',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) +# r = couchdb.view('federation/uid_map',key=uid) +# id = r[0]['value'] +# couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=id,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 +# 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 - session['plans'] - amount = sum([item['amount'] for item in plans]) - apikey = CONFIG['stripe']['pub'].strip() +# session['plans'] +# amount = sum([item['amount'] for item in plans]) +# apikey = CONFIG['stripe']['pub'].strip() - amount = amount / 100 - return render_template('bill.html',context=CONTEXT,apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount) +# amount = amount / 100 +# return render_template('bill.html',context=CONTEXT,apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount) if __name__ == '__main__' : # diff --git a/src/api/static/js/payment.js b/src/api/static/js/payment.js index fc24d24..1641960 100644 --- a/src/api/static/js/payment.js +++ b/src/api/static/js/payment.js @@ -3,9 +3,14 @@ var cards = { "^4\\d{3}(| |-)(?:\\d{4}\\1){2}\\d{4}$":"visa","^6(?:011|5\\d\\d)(| |-)(?:\\d{4}\\1){2}\\d{4}$":"discover"} var payment = {} -payment.setup = function(context,pricing){ +payment.setup = function(context,pricing,product_name){ sessionStorage.store_context = context - sessionStorage.pricing = pricing.constructor == String ? pricing: JSON.stringify(pricing) + if (pricing != null){ + sessionStorage.pricing = pricing.constructor == String ? pricing: JSON.stringify(pricing) + }else{ + sessionStorage.pricing = "[]" + } + sessionStorage.product_name = product_name sessionStorage.customer = JSON.stringify({info:{}}) $('.payment-amount > input').on('focusin',function(){ @@ -27,7 +32,10 @@ payment.setup = function(context,pricing){ } payment.init = function(index){ var p = JSON.parse(sessionStorage.pricing) - + console.log(p) + if(p.length == 0){ + return ; + } var _item = p[index] var amount = (_item.unit_amount/100).toFixed(2) $('.amount').val( amount) @@ -39,8 +47,11 @@ payment.init = function(index){ } payment.validate = function(e){ var id = e.target + console.log([id,e]) customer = JSON.parse(sessionStorage.customer) - + if($(id).attr('class').match(/number|cvc|exp/) && sessionStorage.has_card){ + return 1 ; + } $(id).removeClass('error') var pattern = $(id).attr('data-pattern') var field = $(id).attr('class') @@ -78,6 +89,7 @@ payment.validate = function(e){ sessionStorage.customer = JSON.stringify(customer) return 1 }else{ + // id.className = id.className + ' error' $(id).addClass('error') return 0 } @@ -86,7 +98,7 @@ payment.validate = function(e){ } payment.dialog = function(title,message,type){ ICONS = {'WARN':'fas fa-exclamation-triangle','CHECK':'fas fa-check fa-3x','PROGRESS':'fas fa-cog fa-spin'} - var uri = ([sessionStorage.store_context,'/ui/dialog']).join('') + var uri = ([sessionStorage.store_context,'/html/dialog']).join('') var info = {title:title,message:message,icon:ICONS[type],type:type} var httpclient = HttpClient.instance() @@ -96,22 +108,24 @@ payment.dialog = function(title,message,type){ jx.modal.show({html:x.responseText,id:'payment-dialog'}) }) } -payment.proceed = function(){ +payment.proceed = function(pointer){ - jx.utils.patterns.visitor($('input'),function(_input){ + var values = jx.utils.patterns.visitor($('input'),function(_input){ // // re-running validation - if ($(_input).attr('class') != 'amount'){ - $(_input).keyup() - }else { - $(_input).change() - } + return payment.validate({target:_input}) + // if ($(_input).attr('class') != 'amount'){ + // $(_input).keyup() + // }else { + + // $(_input).change() + // } }) var N = $('input').length info = JSON.parse(sessionStorage.customer).info var n = jx.utils.keys(info).length - if (n != N){ + if (n != jx.math.sum(values) && ! sessionStorage.has_card){ // // Nothing to be done here because the user interface would have lit-up // @@ -123,10 +137,13 @@ payment.proceed = function(){ payment.dialog('Preparing payment','Please wait ...','PROGRESS') info.amount = info.amount * 100 $('.dialog > .message').text('Processing card, please wait ...') - var uri = ([sessionStorage.store_context,'/{{product.name|safe}}/pay']).join('/') + var uri = ([sessionStorage.store_context,sessionStorage.product_name,'pay']).join('/') var http = HttpClient.instance() - http.setData(JSON.stringify(info)) - http.setHeader('content-type','application/json') + if(sessionStorage.has_card == "1"){ + info.has_card = true + } + console.log(info) + http.setData(info,"application/json") http.post(uri,function(x){ if(x.status == 200){ @@ -143,9 +160,38 @@ payment.proceed = function(){ TITLE='.::' TYPE='WARN' } - $('.jxmodal')[$('.jxmodal')].remove() + $('.jxmodal')[$('.jxmodal').length -1 ].remove() payment.dialog(TITLE,x.responseText,TYPE) + if (payment.do_post !=null){ + payment.do_post() + } }) } +} + +payment.card_onfile = function(){ + + var name = $('.card_onfile > i').attr('class') + if (name.match(/square/i)){ + $('.card_onfile > i').attr('class','fas fa-check') + $('.number').slideUp( function(){ + $('.expire').slideUp() + }) + var pointer = function(){} + sessionStorage.has_card = 1 + }else{ + delete sessionStorage.has_card + $('.card_onfile > i').attr('class','far fa-square') + $('.number').slideDown( function(){ + $('.expire').slideDown() + }) + pointer = function(){ + payment.validate(event) + } + + } + jx.utils.patterns.visitor(['.number','.exp','.cvc'],function(id){ + $(id).click = pointer + }) } \ No newline at end of file diff --git a/src/api/store.py b/src/api/store.py index c1144e2..c738e18 100644 --- a/src/api/store.py +++ b/src/api/store.py @@ -28,12 +28,21 @@ class User : """ try: card = dict(_args['card'] ) + + values = card['exp'].split('/') if 'exp' in card else None,None + + if 'exp' in card : + + card['exp_month'] = card['exp'].split('/')[0] + card['exp_year'] = int(card['exp'].split('/')[1]) + del card['exp'] for field in ['name','email'] : - if field in card : + if field in card : + self.me['customer'][field] = card[field] del card[field] - # + # tok_1HPyukEUWsmgY81ALQuKGBmT # address information should be added to the card # # save_card = _args['save'] if 'save' in _args else 0 @@ -47,14 +56,32 @@ class User : del card['currency'] else: currency = 'usd' - r = stripe.source.Source.create(type='card',currency=currency,card=card) + for item in self.me['payments'] : + stripe.customer.Customer.delete_source(self.me['customer']['id'],item['id']) + # + # + + owner = {"email":self.me['customer']['email'],"name":self.me['customer']['name']} + # r = stripe.source.Source.create(type='card',currency=currency,card=card) + r = stripe.token.Token.create(card=card) + + o = stripe.source.Source.create(type='card',currency=currency,owner=owner,token=r.id,usage='reusable') + o = stripe.customer.Customer.create_source(self.me['customer']['id'],source=o.id) + # r = stripe.source.Source.create(type='card',currency=currency,card=card) self.me['payments'] = [r.card.to_dict_recursive()]+self.me['payments'] self.init(self.me["customer"]['email']) return r except Exception as error : - print (error) + print ([' ** ',error]) return 0 - + def invoices(self): + # r = stripe.charge.Charge.list(customer=self.me['customer']['id']).data + r = [] + _logs = stripe.invoice.Invoice.list(customer=self.me['customer']['id']) + for item in _logs.auto_paging_iter() : + r += [{"id":item.id,"date":item.date*1000,"amount":item.amount_paid,"paid":item.paid,"status":item.status,"label":item.lines.data[0].description,"invoice":item.id,"pdf":item.invoice_pdf,"hosted":item.hosted_invoice_url} ] + # r += [{"id":item.id,"date":item.created*1000,"amount":item.amount,"paid":item.paid,"status":item.status,"label":item.statement_descriptor if item.statement_descriptor else item.calculated_statement_descriptor,"invoice":item.invoice} for item in logs] + return r def charge(self,**args) : """ This function will charge a user for a service (given a plan id) @@ -118,7 +145,7 @@ class User : self.card.exists = lambda: len(self.me["payments"]) > 0 self.card.add = self._add_card self.card.charge = self.charge - self.card.html = lambda: [{} for card in self.me["payments"]] + self.card.html = lambda: [{"number":card['last4'],"type":card['brand']} for card in self.me["payments"]] self.card.delete = lambda index: stripe.customer.Customer.delete_source(self.me["customer"]['id'],self.me["payment"][index]) def init(self,email): @@ -154,7 +181,7 @@ class User : if 'sources' in customer and 'data' in customer['sources'] : if customer['sources']['data'] : # self.payment = [ card.to_dict_recursive() for card in customers['sources']['data'] ] - self.me['payments'] = [ card.to_dict_recursive() for card in customer['sources']['data'] ] + self.me['payments'] = [ card.card.to_dict_recursive() if 'card' in card else card.card.to_dict_recursive() for card in customer['sources']['data'] ] # if not self._has_plan(customer) and free_plan: # free_plan = free_plan[0] # self.subscribe(free_plan['id'],email) @@ -291,15 +318,18 @@ class Plans(Store) : _item['amount'] = item['unit_amount'] if item.recurring == None : self.checkout.append(_item) - elif 'features' in item.metadata : - _item['metadata']['features'] = json.loads(item.metadata['features']) + else: self.plans.append(_item) + if 'features' in item.metadata : + _item['metadata']['features'] = json.loads(item.metadata['features']) + else: + _item['metadata']['features'] = {} + # self.plans.append(_item) # index = self.plans.index(item) # self.plans[index] = item.to_dict_recursive() # if 'features' in self.plans[index]['metadata'] : # self.plans[index]['metadata']['features'] = json.loads(self.plans[index]['metadata']['features']) # elif self.plans - self.plans.sort(key=lambda item: item['unit_amount']) self.checkout.sort(key=lambda item:item['unit_amount']) if 'email' in args : @@ -373,7 +403,7 @@ class Plans(Store) : self.user.init(email) # # We must insure the user is one of our customers i.e perhaps - if amount == 0 or self.payment : + if amount == 0 or self.payment and plan: r = stripe.subscription.Subscription.create( customer = self.user.info()['id'], items = [plan] @@ -394,4 +424,4 @@ class factory: # p.user.subscribe = p.subscribe return p - \ No newline at end of file + diff --git a/src/api/templates/card.html b/src/api/templates/card.html index 062018c..6eb5de4 100644 --- a/src/api/templates/card.html +++ b/src/api/templates/card.html @@ -3,16 +3,24 @@ - + - + + + + {% if not product.metadata.label %} + {{product.name}} + {% else %} + {{product.metadata.label}} + {% endif %} +