转载

Python和Flask真是太强大了,百余行Flask代码的实战案例

作为一个程序员,我有时候忘了自己所具有的能力。当事情没有按照你想要的方式发展时,却很容易忘记你有能力去改变它。昨天,我意识到,我已经对我所出售的书的付款处理方式感到忍无可忍了。我的书完成后,我使用了三个不同的数字商品支付处理器,在对它们三个都感到不满后,我用Python和Flask,两个小时的时间写出了我自己的解决方案。没错!两个小时!现在,这个系统支撑着我的书籍付费流程,整个过程难以置信的简单,你可以在20秒内购买书籍并开始阅读。

往下看,看我是如何在一夜之间完成我自己的数字商品支付解决方案的。

支付处理器的付款问题

在我开始卖书的时候,我综合用了两种支付服务(一种是信用卡,另一种是PayPal)。最终,我发现了一个可以支持两者的处理方式。可是我对它们都不满意。最常用的那个支付处理器,要求用户在销售商的系统中创建一个账户,并且输入他们的邮箱地址(尽管邮箱并没有用)。

另外,我尝试使用Google Analytics 在全部的访问中追踪访客,包括他们的结账过程,这一过程也很艰辛。我经常感觉到,如果我能够让它工作起来,并且能够在我的书籍页面进行A/B测试,我能极大地提高销量。但是因为不能很好的追踪,我就没那么走运了。

最后,使用三个不同的支付处理器,发送书籍更新非常耗时。没有一个能很好的支持更新,而我希望有个“一键”解决方案来发送我的书籍更新。我就没找到一个类似的服务。

欧耶,我是个程序员

昨天,当我收到一个顾客的邮件,抱怨支付过程是如何的困难并且告诉我可能因此损失了很多销量后,我忍无可忍了。我决定整一个自己的数字商品管理解决方案。我需要做到下面这样了流程:

当客户点击“Buy Now”按钮后,他们应该仅仅被要求输入他们的email地址和信用卡信息。点击“Confirm”后被带到一个独一无二的URL去下载书籍(专门为了这次交易而生成的)。一封包含这个URL的邮件应该被发送给客户(防止用户需要重新下载这本书)。对他们重复下载的次数应该有个限制(5次)。交易信息和客户信息应该被存放在数据库中,发送书籍更新应该只是一个命令的事。

显然,这并不是那么的复杂。最复杂的部分是动态生成导向特定书籍版本的独一无二的URL。其他的事情都挺简单的。

“Flask前来救援”或是“一个100行代码的数字商品支付解决方案”

剧透:程序的最终结果正好是100行代码。对于这种规模的web应用,Flask是个很好的选择。并不需要大量的模板( cough 就像 Django cough ),但是有很多很好的插件作为支持。Bottle会是另外一个不错的选择,但是我最近都在用Flask,所以我就选它了。

开始的时候,我需要决定如何来存放用户和交易信息。我决定使用 SQLAlchemy ,因为 sandman 的原因,我比较熟悉它SQLAlchemy。Flask有一个插件叫Flask-SQLAlchemy,这使得结合使用两者非常容易。因为我不需要任何花哨的数据库操作,我选择SQLite作为我的后台数据库。

决定这样做之后,我创建了一个 app.py 文件并且创建了如下的模型:

class Product(db.Model):  __tablename__ = 'product'  id = db.Column(db.Integer, primary_key=True)  name = db.Column(db.String)  file_name = db.Column(db.String)  version = db.Column(db.String)  is_active = db.Column(db.Boolean, default=True)  price = db.Column(db.Float) class Purchase(db.Model):  __tablename__ = 'purchase'  uuid = db.Column(db.String, primary_key=True)  email = db.Column(db.String)  product_id = db.Column(db.Integer, db.ForeignKey('product.id'))  product = db.relationship(Product)  downloads_left = db.Column(db.Integer, default=5) 

在向数据库添加了5种不同版本的书籍后(我创建了一个 populate_db.py 文件,并把这些版本作为SQLAlchemy模型来添加进数据库),我需要决定我究竟要如何处理支付。幸运的是,Stripe让接受一个信用卡变得非常简单,并且我也已经有了一个他们的账户。他们的”checkout.js”方案会在你的页面上创建一个表单和按钮。当点击这个按钮时,一个简洁又引人注目的浮动层会弹出来。

Python和Flask真是太强大了,百余行Flask代码的实战案例

这个表单的 action 属性指向你的站点的一个页面,当用户完成支付后就会被带到那里。我在我的书籍销售页面添加了5个这样的按钮,还有一个隐藏的表单栏,包含了被交易产品的id( product_id )(1-5之间的一个整数)

处理支付

显然,我的应用需要一个后端来处理一次成功付款。我添加了以下这些函数来完成这一目的:

@app.route('/buy', methods=['POST']) def buy():     stripe_token = request.form['stripeToken']     email = request.form['stripeEmail']     product_id = request.form['product_id']     product = Product.query.get(product_id)     try:  charge = stripe.Charge.create(   amount=int(product.price * 100),   currency='usd',   card=stripe_token,   description=email)     except stripe.CardError, e:  return """<html><body><h1>Card Declined</h1><p>Your chard could not  be charged. Please check the number and/or contact your credit card  company.</p></body></html>"""     print charge     purchase = Purchase(uuid=str(uuid.uuid4()),      email=email,      product=product)     db.session.add(purchase)     db.session.commit()     message = Message(      subject='Thanks for your purchase!',  sender="jeff@jeffknupp.com",   html="""<html><body><h1>Thanks for buying Writing Idiomatic Python!</h1> <p>If you didn't already download your copy, you can visit  <a href="http://buy.jeffknupp.com/{}">your private link</a>. You'll be able to download the file up to five times, at which point the link will expire.""".format(purchase.uuid),  recipients=[email])     with mail.connect() as conn:  conn.send(message)     return redirect('/{}'.format(purchase.uuid)) 

正如你所看到的,我写代码的时候有点偷懒了(因为我正在愤怒的编程……)首先,我有一个内联HTML,在付费不成功时返回,还有已购买成功时返回邮箱。这些东西应该被放在一个全局变量,或者,更好的方法是放在一个单独的文件中。另外,在创建 Purchase 时我并没有进行任何的错误检查。但是实际上,唯一可能出错的地方是尝试插入重复的 uuid ,但是我并不担心,因为发生的几率太低了(潜台词:微乎其微)。

你可以看的我使用了一个 mail 对象,这个对象来自于Flask-Mail包,这个插件让发送邮件变得轻松。我就设置它使用GMail作为服务器,然后所有东西都可以正常工作了。

好吧,现在该给我书了

现在,支付的部分已经搞定,我需要添加一个后端功能,在完成支付后初始化下载。因为我使用UUID作为主键,所以我同样可以使用它作为URL。当有人访问包含UUID的URL时,我需要检查该UUID是不是包含在数据库中。如果是的话,提供书籍文件并且把剩余下载( downloads_left )次数属性减少1次。如果不是的话,就返回404错误。下面是我写的代码:

@app.route('/<uuid>') def download_file(uuid):  purchase = Purchase.query.get(uuid)  if purchase:   if purchase.downloads_left <= 0:    return """<html><body><h1>No downloads left!</h1><p>You have    exceeded the allowed number of downloads for this file. Please email    jeff@jeffknupp.com with any questions.</p></body></html>"""   purchase.downloads_left -= 1   db.session.commit()   return send_from_directory(directory='files',     filename=purchase.product.file_name, as_attachment=True)  else:   abort(404) 

非常直观。使用UUID作为一个URL变量,寻找交易信息。如果存在,就检查是否还有可用下载次数,然后提供所购买的文件。否则,等着你的是404错误。

最后,我需要我需要添加一个测试来让我可以模拟交易过程。下面是测试代码和让这个app运行的代码:

@app.route('/test') def test():  return """<http><body><form action="buy" method="POST"> <script  src="https://checkout.stripe.com/checkout.js" class="stripe-button"  data-key="pk_test_w3qNBkDR8A4jkKejBmsMdH34"  data-amount="999"  data-name="jeffknupp.com"  data-description="Writing Idiomatic Python 3 PDF ($9.99)"> </script> <input type="hidden" name="product_id" value="2" /> </form> </body> </html> """ if __name__ == '__main__':  sys.exit(app.run(debug=True)) 

能力越大…责任越大!

实际上我对于自己能如此快速简单让这一切工作起来感到吃惊。整个应用程序包含在一个100行代码的文件中。而且它替代了我每天使用的那些重要服务,我对那些服务一直都不满意。最后,我可以追踪交易,没有任何问题,这让我确信自己可以提高销量。

作为一个开发者,能意识到你有能力塑造我们和数字世界的交互是很好的一件事。比方说,我会忘记,如果有一些科技不能按照我预想的方式去工作,我有能力改变它。从自动化机械式的任务比如输入数据,到自动排序和整理电子邮件,开发者们有能力去简化他们每天的工作。

拥有Flask这样的工具对解决这些问题非常重要。正如像作为程序员那样进步,你应该建立你的一套解决“核心”问题的工具集。Flask就是很好的例子,因为匆匆忙忙的拼凑一个web应用是一件司空见惯的事情。

当然,分享你的作品同样非常的重要。如果我做一些东西,对我自己有用,但没有去分享给别人,我就会怠慢。“分享”不仅仅意味着”把项目放进一个GitHub公共仓库“。你还需要让大家知道有这个东西。从邮件列表到论坛再到个人博客,从来都不缺少让大家知道你创造了一些东西的途径。我总是设法回馈社区,因为我从中得到了很多东西。

Jeff Knupp,发表于2014年1月18日

正文到此结束
Loading...