You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.6 KiB
201 lines
7.6 KiB
from flask import render_template, flash, redirect, g, request, url_for, jsonify
|
|
from app import app, db
|
|
from models import Ticket, Invoice
|
|
from forms import TicketForm
|
|
from flask.ext.security import login_required, current_user
|
|
from config import BLOCKCHAIN_URL, SECRET_KEY, STASH_WALLET, PRICE_OF_SERVICE, CONFIRMATION_CAP
|
|
import simplejson as json
|
|
import urllib2
|
|
|
|
# Page routes
|
|
@app.route('/')
|
|
@app.route('/index')
|
|
def index():
|
|
user = g.user
|
|
return render_template("index.html", user=user)
|
|
|
|
@app.route('/blog')
|
|
def blog():
|
|
return "Da Blog!"
|
|
|
|
@app.route('/dashboard')
|
|
@login_required
|
|
def dashboard():
|
|
user = g.user
|
|
latest_invoice = user.invoices.order_by(Invoice.datepaid.desc()).first()
|
|
lastpaid = latest_invoice.datepaid if latest_invoice else "Unpurchased"
|
|
expires = latest_invoice.dateends if latest_invoice else "No service enabled"
|
|
return render_template("dashboard.html", user=user, latest=latest_invoice, lastpaid=lastpaid, expires=expires)
|
|
|
|
@app.route('/newticket', methods=['GET', 'POST'])
|
|
@login_required
|
|
def newticket():
|
|
user = g.user
|
|
form = TicketForm()
|
|
if form.validate_on_submit():
|
|
import datetime
|
|
t = Ticket()
|
|
form.populate_obj(t)
|
|
t.timestamp = datetime.datetime.utcnow()
|
|
t.created = datetime.datetime.utcnow()
|
|
t.user_id = user.id
|
|
db.session.add(t);
|
|
db.session.commit()
|
|
|
|
flash('New ticket submitted: ' + form.subject.data)
|
|
return redirect('/dashboard')
|
|
return render_template('newticket.html', form=form, user=user)
|
|
|
|
@app.route('/viewticket/<int:tid>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def viewticket(tid):
|
|
user = g.user
|
|
t = Ticket.query.get(tid)
|
|
return render_template('viewticket.html', user=user, ticket=t)
|
|
|
|
@app.route('/editticket/<int:tid>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def editticket(tid):
|
|
user = g.user
|
|
t = Ticket.query.get(tid)
|
|
form = TicketForm(subject=t.subject, body=t.body)
|
|
if form.validate_on_submit():
|
|
import datetime
|
|
form.populate_obj(t)
|
|
t.timestamp = datetime.datetime.utcnow()
|
|
flash("Updated ticket: " + t.subject)
|
|
db.session.commit()
|
|
return redirect('/dashboard')
|
|
return render_template('editticket.html', user=user, ticket=t, form=form)
|
|
|
|
@app.route('/deleteticket/<int:tid>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def deleteticket(tid):
|
|
user = g.user
|
|
t = Ticket.query.get(tid)
|
|
return render_template('viewticket.html', user=user, ticket=t)
|
|
|
|
# Start Bitcoin stuff -- blockchain.info api
|
|
@app.route('/purchase')
|
|
def purchase():
|
|
user = g.user
|
|
try:
|
|
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value=30")))
|
|
except urllib2.URLError as e:
|
|
flash('Unable to fetch current BTC exchange rate.')
|
|
return render_template('purchase.html', user=user, price=0, service_price=PRICE_OF_SERVICE)
|
|
return render_template('purchase.html', user=user, price=str(exchange_data), service_price=PRICE_OF_SERVICE)
|
|
|
|
@app.route('/confirm_purchase/', defaults={'invoice_id': None})
|
|
@app.route('/confirm_purchase/<int:invoice_id>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def confirm_purchase(invoice_id):
|
|
import datetime
|
|
user = g.user
|
|
if invoice_id is None:
|
|
i = Invoice()
|
|
i.paid = False
|
|
i.datecreated = datetime.datetime.utcnow()
|
|
i.user_id = user.id
|
|
db.session.add(i)
|
|
db.session.commit()
|
|
try:
|
|
callback_url = url_for('pay_invoice', _external=True)+'?secret='+SECRET_KEY+'%26invoice_id='+str(i.id)
|
|
url = BLOCKCHAIN_URL+'?method=create&address='+STASH_WALLET+'&callback='+callback_url
|
|
xhr = urllib2.Request(url)
|
|
data = json.load(urllib2.build_opener().open(xhr))
|
|
price_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value="+str(PRICE_OF_SERVICE))))
|
|
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/ticker")))
|
|
app.logger.info("Sent to blockchain api: " + url)
|
|
except urllib2.URLError as e:
|
|
app.logger.error('Unable to access the blockchain.info api: ' + url)
|
|
flash('There was an error creating a new invoice. Please try again later.')
|
|
return redirect('/dashboard')
|
|
i.address = data['input_address']
|
|
i.total_btc = price_data
|
|
i.exchange_rate_when_paid = exchange_data['USD']['last']
|
|
db.session.commit()
|
|
# TODO: Generate a QR code and/or other e-z payment options for BTC services
|
|
return redirect(url_for('confirm_purchase', invoice_id=i.id))
|
|
else:
|
|
i = Invoice.query.get(invoice_id)
|
|
if request.method == 'POST':
|
|
flash('Invoice ('+i.address+') was deleted succesfully.')
|
|
db.session.delete(i)
|
|
db.session.commit()
|
|
return redirect(url_for('dashboard'))
|
|
return render_template('confirm_purchase.html', user=user, invoice=i, min_confirm=CONFIRMATION_CAP)
|
|
|
|
|
|
# AJAX Callbacks
|
|
@app.route('/invoice_status', methods=['POST'])
|
|
def invoice_status():
|
|
data = request.form
|
|
i = Invoice.query.get(data['invoice_id'])
|
|
if i is None:
|
|
return 0
|
|
return jsonify({
|
|
'confirmations': i.confirmations,
|
|
'value_paid': i.value_paid,
|
|
'total_btc': i.total_btc,
|
|
'input_transaction_hash': i.input_transaction_hash
|
|
})
|
|
|
|
@app.route('/pay_invoice', methods=['GET'])
|
|
def pay_invoice():
|
|
data = request.args
|
|
if 'test' in data:
|
|
app.logger.info('Test response recieved from Blockchain.info. return: *test*')
|
|
return "*test*"
|
|
if 'secret' in data and data['secret'] == SECRET_KEY:
|
|
import datetime
|
|
i = Invoice.query.get(data['invoice_id'])
|
|
if i is None:
|
|
# could not find invoice - do we ignore or create?
|
|
app.logger.info("Callback received for non-existant invoice. return: *error*")
|
|
return "*error*"
|
|
if not i.paid:
|
|
i.value_paid = float(data['value']) / 100000000
|
|
else:
|
|
i.value_paid += float(data['value']) / 100000000
|
|
i.datepaid = datetime.datetime.utcnow()
|
|
i.confirmations = data['confirmations']
|
|
i.transaction_hash = data['transaction_hash']
|
|
i.input_transaction_hash = data['input_transaction_hash']
|
|
if i.value_paid == i.total_btc:
|
|
app.logger.info("Invoice {} paid on {} for {} BTC.".format(i.id, i.datepaid, i.value_paid))
|
|
i.paid = True
|
|
db.session.commit()
|
|
if i.paid and i.confirmations > CONFIRMATION_CAP:
|
|
app.logger.info("Invoice {} was confirmed at {}. return: *ok*".format(i.id, i.datepaid))
|
|
i.is_confirmed = True
|
|
i.dateends = i.datepaid + datetime.timedelta(weeks=4)
|
|
return "*ok*"
|
|
app.logger.info("Callback received for invoice {}: awaiting confirmation (current: {}). return: *unconfirmed*".format(i.id, i.confirmations))
|
|
return "*unconfirmed*"
|
|
else:
|
|
app.logger.info('Payment callback with invalid secret key recieved. return: *error*')
|
|
return "*error*"
|
|
|
|
# Not routes
|
|
@app.errorhandler(404)
|
|
def internal_error(error):
|
|
return render_template('error.html'), 404
|
|
|
|
@app.errorhandler(500)
|
|
def internal_error(error):
|
|
db.session.rollback()
|
|
return render_template('error.html'), 500
|
|
|
|
@app.before_request
|
|
def before_request():
|
|
g.user = current_user
|
|
|
|
@app.template_filter('date')
|
|
def _jinja2_filter_datetime(date, fmt=None):
|
|
if not date:
|
|
return None
|
|
if fmt:
|
|
return date.strftime(fmt)
|
|
else:
|
|
return date.strftime("%m/%d/%y (%I:%M%p)")
|