利用Python的Flask框架来构建一个简单的数字商品支付解决方案


Posted in Python onMarch 31, 2015

作为一个程序员,我有时候忘了自己所具有的能力。当事情没有按照你想要的方式发展时,却很容易忘记你有能力去改变它。昨天,我意识到,我已经对我所出售的书的付款处理方式感到忍无可忍了。我的书完成后,我使用了三个不同的数字商品支付处理器,在对它们三个都感到不满后,我用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框架来构建一个简单的数字商品支付解决方案

这个表单的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公共仓库“。你还需要让大家知道有这个东西。从邮件列表到论坛再到个人博客,从来都不缺少让大家知道你创造了一些东西的途径。我总是设法回馈社区,因为我从中得到了很多东西。

Python 相关文章推荐
在Python中使用判断语句和循环的教程
Apr 25 Python
python编程之requests在网络请求中添加cookies参数方法详解
Oct 25 Python
Python用户推荐系统曼哈顿算法实现完整代码
Dec 01 Python
python实现括号匹配的思路详解
Aug 23 Python
python将list转为matrix的方法
Dec 12 Python
python调用动态链接库的基本过程详解
Jun 19 Python
pytorch中的transforms模块实例详解
Dec 31 Python
Python tkinter模版代码实例
Feb 05 Python
Python如何输出整数
Jun 07 Python
在keras里实现自定义上采样层
Jun 28 Python
python使用yaml 管理selenium元素的示例
Dec 01 Python
如何使用Python对NetCDF数据做空间相关分析
Apr 21 Python
用Python进行基础的函数式编程的教程
Mar 31 #Python
python使用多线程不断刷新网页的方法
Mar 31 #Python
Python新手实现2048小游戏
Mar 31 #Python
举例介绍Python中的25个隐藏特性
Mar 30 #Python
在Python的循环体中使用else语句的方法
Mar 30 #Python
python实现2048小游戏
Mar 30 #Python
利用一个简单的例子窥探CPython内核的运行机制
Mar 30 #Python
You might like
php的大小写敏感问题整理
2011/12/29 PHP
学习php设计模式 php实现抽象工厂模式
2015/12/07 PHP
php实现快速对二维数组某一列进行组装的方法小结
2019/12/04 PHP
JavaScript DOM 添加事件
2009/02/14 Javascript
Javascript 中介者模式实例
2009/12/16 Javascript
关于JavaScript定义类和对象的几种方式
2010/11/09 Javascript
iframe 异步加载技术及性能分析
2011/07/19 Javascript
jquery 图片缩放拖动的简单实例
2014/01/08 Javascript
input:checkbox多选框实现单选效果跟radio一样
2014/06/16 Javascript
JavaScript bold方法入门实例(把指定文字显示为粗体)
2014/10/17 Javascript
JavaScript数组合并的多种方法
2016/05/22 Javascript
JS制作类似选项卡切换的年历
2016/12/03 Javascript
jquery.uploadView 实现图片预览上传功能
2017/08/10 jQuery
微信小程序引用公共js里的方法的实例详解
2017/08/17 Javascript
使用vue中的v-for遍历二维数组的方法
2018/03/07 Javascript
webpack公共组件引用路径简化小技巧
2018/06/15 Javascript
vue实现输入框的模糊查询的示例代码(节流函数的应用场景)
2019/09/01 Javascript
解决vue的router组件component在import时不能使用变量问题
2020/07/26 Javascript
el-table表头根据内容自适应完美解决表头错位和固定列错位
2021/01/07 Javascript
[51:00]Secret vs VGJ.S 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
Python实现简单字典树的方法
2016/04/29 Python
开源软件包和环境管理系统Anaconda的安装使用
2017/09/04 Python
轻松理解Python 中的 descriptor
2017/09/15 Python
Tensorflow中使用tfrecord方式读取数据的方法
2018/06/19 Python
python实现对指定字符串补足固定长度倍数截断输出的方法
2018/11/15 Python
对python For 循环的三种遍历方式解析
2019/02/01 Python
python中的colorlog库使用详解
2019/07/05 Python
css3实现input输入框颜色渐变发光效果代码
2014/04/02 HTML / CSS
CSS3与动画有关的属性transition、animation、transform对比(史上最全版)
2017/08/18 HTML / CSS
HTML5中Localstorage的使用教程
2015/07/09 HTML / CSS
Python中pass语句的作用是什么
2016/06/01 面试题
辞职信如何写
2015/02/27 职场文书
工厂门卫岗位职责
2015/04/13 职场文书
2015大学党建带团建工作总结
2015/07/23 职场文书
汽车修理厂管理制度
2015/08/05 职场文书
高二英语教学反思
2016/03/03 职场文书