之前一直想记录一下在项目中使用到的事务以及支付宝支付功能,自己一直犯懒没有完,趁今天有点兴致,在这记录一下。

商城项目必备的就是支付订单的功能,所以就会涉及到订单的保存以及支付接口的引入。先来看看订单的保存,在数据库模型涉及之初,将订单分成了两个表,一个为订单表,记录订单的基本信息,如订单号,用户信息,运费之类,一个为订单商品表,记录该订单中的商品信息。在保存订单时,肯定会涉及到两个表的新建和保存,其实还有一张表也需要进行一些修改,那就是商品表,当一个订单保存成功,意味着本次交易成功,商品售出,商品的库存应该进行修改。所以,在保存订单这一操作中,涉及到的表有三张。所以在保存订单时,多表数据的修改,要嘛同时成功,要嘛同时失败,这就跟数据库中的事务很像,因此,在这里引入事务,来完成订单保存的功能。

在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法,一种是装饰器,一种是with语句。

  1. from django.db import transaction
  2.  
  3. @transaction.atomic
  4. def viewfunc(request):
  5. # 这些代码会在一个事务中执行
  6. ...
  1. from django.db import transaction
  2.  
  3. def viewfunc(request):
  4. # 这部分代码不在事务中,会被Django自动提交
  5. ...
  6.  
  7. with transaction.atomic():
  8. # 这部分代码会在事务中执行
  9. ...    

在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以恢复到数据保存点的状态

  1. from django.db import transaction
  2.  
  3. # 创建保存点
  4. save_id = transaction.savepoint()
  5.  
  6. # 回滚到保存点
  7. transaction.savepoint_rollback(save_id)
  8.  
  9. # 提交从保存点到当前状态的所有数据库事务操作
  10. transaction.savepoint_commit(save_id)

所以,可以在序列化器的create方法中,创建一个事务,还进行数据的修改保存还有新建,若有地方出错,则直接回滚,若没有问题则提交事务。代码如下

  1. def create(self, validated_data):
  2. """
  3. 保存订单
  4. """
  5. # 获取当前下单用户
  6. user = self.context['request'].user
  7. # 组织订单编号 20170903153611+user.id
  8. # timezone.now() -> datetime
  9. order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
  10. address = validated_data['address']
  11. pay_method = validated_data['pay_method']
  12. # 生成订单
  13. with transaction.atomic():
  14. # 创建一个保存点
  15. save_id = transaction.savepoint()
  16. try:
  17. # 创建订单信息
  18. order = OrderInfo.objects.create(
  19. order_id=order_id,
  20. user=user,
  21. address=address,
  22. total_count=0,
  23. total_amount=Decimal(0),
  24. freight=Decimal(10),
  25. pay_method=pay_method,
  26. status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID']
  27. )
  28. # 获取购物车信息
  29. redis_conn = get_redis_connection("cart")
  30. redis_cart = redis_conn.hgetall("cart_%s" % user.id)
  31. cart_selected = redis_conn.smembers('cart_selected_%s' % user.id)
  32. # 将bytes类型转换为int类型
  33. cart = {}
  34. for sku_id in cart_selected:
  35. cart[int(sku_id)] = int(redis_cart[sku_id])
  36. # # 一次查询出所有商品数据
  37. # skus = SKU.objects.filter(id__in=cart.keys())
  38. # 处理订单商品
  39. sku_id_list = cart.keys()
  40. for sku_id in sku_id_list:
  41. while True:
  42. sku = SKU.objects.get(id=sku_id)
  43. sku_count = cart[sku.id]
  44. # 判断库存
  45. origin_stock = sku.stock # 原始库存
  46. origin_sales = sku.sales # 原始销量
  47. if sku_count > origin_stock:
  48. transaction.savepoint_rollback(save_id)
  49. raise serializers.ValidationError('商品库存不足')
  50. # 用于演示并发下单
  51. # import time
  52. # time.sleep(5)
  53. # 减少库存
  54. # sku.stock -= sku_count
  55. # sku.sales += sku_count
  56. # sku.save()
  57. new_stock = origin_stock - sku_count
  58. new_sales = origin_sales + sku_count
  59. # 根据原始库存条件更新,返回更新的条目数,乐观锁
  60. ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
  61. if ret == 0:
  62. continue
  63. # 累计商品的SPU 销量信息
  64. sku.goods.sales += sku_count
  65. sku.goods.save()
  66. # 累计订单基本信息的数据
  67. order.total_count += sku_count # 累计总金额
  68. order.total_amount += (sku.price * sku_count) # 累计总额
  69. # 保存订单商品
  70. OrderGoods.objects.create(
  71. order=order,
  72. sku=sku,
  73. count=sku_count,
  74. price=sku.price,
  75. )
  76. # 更新成功
  77. break
  78. # 更新订单的金额数量信息
  79. order.total_amount += order.freight
  80. order.save()
  81. except serializers.ValidationError:
  82. raise
  83. except Exception as e:
  84. logger.error(e)
  85. transaction.savepoint_rollback(save_id)
  86. raise
  87. # 提交事务
  88. transaction.savepoint_commit(save_id)
  89. # 更新redis中保存的购物车数据
  90. pl = redis_conn.pipeline()
  91. pl.hdel('cart_%s' % user.id, *cart_selected)
  92. pl.srem('cart_selected_%s' % user.id, *cart_selected)
  93. pl.execute()
  94. return order

还有一点需要注意的是,当订单提交,购物车中相应的商品应该进行删除。好了,以上就是django中的事务。

再来说说支付宝支付功能的引入,现在基本上所有的项目涉及到支付功能时都会引入第三方支付,其中使用最广泛的应该就是支付宝和微信了,这里我使用的是支付宝支付。当订单创建完成,接下来就是支付了。

支付宝开发平台网址https://open.alipay.com/platform/home.htm。由于开发者账户需要经过审核,支付宝有提供沙箱环境,来进行测试。沙箱应用:https://docs.open.alipay.com/200/105311。沙箱账号:https://openhome.alipay.com/platform/appDaily.htm?tab=account。python对接支付宝SDK:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md。SDK:https://docs.open.alipay.com/270/106291/

先来缕一下流程,用户点击按钮请求支付宝支付界面,先进行登录,登录成功后进行支付操作,支付成功会进行回调。

首先第一步,用户点击按钮,后端会进行url的拼接,将拼接好的url返给前端,前端进行跳转,跳转到支付宝相关界面,用户进行登录和支付等操作。

查看pythonsdk,首先我们可以通过openssl命令生成一个密钥(公钥和私钥),私钥自己留存,公钥用户校验。命令如下:

  1. openssl
  2. OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥
  3. OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
  4. OpenSSL> exit
  5.  
  6. 同时,你要从支付宝获得一个公钥字符串,格式可参考:https://github.com/fzlee/alipay/blob/master/tests/certs/ali/ali_public_key.pem由于上面我们用的是RSA生成的密钥,所以在支付宝中我们也需要RSA的公钥
  1.  

设置好了密钥,我们就可以开始写视图,代码如下:

  1. class PaymentView(APIView):
  2. """
  3. 支付
  4. """
  5. permission_classes = (IsAuthenticated,)
  6.  
  7. def get(self, request, order_id):
  8. """
  9. 获取支付链接
  10. """
  11. # 判断订单信息是否正确
  12. try:
  13. order = OrderInfo.objects.get(order_id=order_id, user=request.user,
  14. pay_method=OrderInfo.PAY_METHODS_ENUM["ALIPAY"],
  15. status=OrderInfo.ORDER_STATUS_ENUM["UNPAID"])
  16. except OrderInfo.DoesNotExist:
  17. return Response({'message': '订单信息有误'}, status=status.HTTP_400_BAD_REQUEST)
  18.  
  19. # 构造支付宝支付链接地址
  20. alipay = AliPay(
  21. appid=settings.ALIPAY_APPID,
  22. app_notify_url=None, # 默认回调url
  23. app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"),
  24. alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem"), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
  25. sign_type="RSA2", # RSA 或者 RSA2
  26. debug=settings.ALIPAY_DEBUG # 默认False
  27. )
  28.  
  29. order_string = alipay.api_alipay_trade_page_pay(
  30. out_trade_no=order_id,
  31. total_amount=str(order.total_amount),
  32. subject="美多商城%s" % order_id,
  33. return_url="http://www.meiduo.site:8080/pay_success.html",
  34. )
  35. # 需要跳转到https://openapi.alipay.com/gateway.do? + order_string
  36. # 拼接链接返回前端
  37. alipay_url = settings.ALIPAY_URL + "?" + order_string
  38. return Response({'alipay_url': alipay_url})

相关的参数可以提前在配置文件中配置好(ALPAY_APPID,ALPAY_URL,ALPAY_DEBUG)注意ALPAY为True时才启用沙箱环境。当用户支付成功,会对你填写的回调网址进行回调,返回的参数如下图。

  1. 前端页面将此数据发送给后端,后端检验并保存支付结果。以上就是全部过程。具体的过程可以参考pythonsdk
  2.  
  3. 本人QQ595395786,欢迎交流!!
  1.  

django中使用事务以及接入支付宝支付功能的更多相关文章

  1. ThinkPHP接入支付宝支付功能

    最近做系统,需要实现在线支付功能,毫不犹豫,选择的是支付宝的接口支付功能.这里我用的是即时到帐的接口,具体实现的步骤如下: 一.下载支付宝接口包 下载地址:https://b.alipay.com/o ...

  2. 支付宝支付功能(使用支付宝sdk)

    1.准备参数        新建一个公共参数配置类NewAlipayconfig (可将参数存放到config配置文件中读取)          public class NewAlipayconfi ...

  3. Android 接入支付宝支付实现

    接上篇android接入微信支付文章,这篇我们带你来接入支付宝支付服务 简介 首先要说明的是个人感觉接入支付宝比微信简单多了,很轻松的,所以同学们不要紧张~ 当然还是老规矩啦,上来肯定的贴上官网地址, ...

  4. Android接入支付宝支付实现

    接上篇android接入微信支付文章,这篇我们带你来接入支付宝支付服务 简介 首先要说明的是个人感觉接入支付宝比微信简单多了,很轻松的,所以同学们不要紧张~ 当然还是老规矩啦,上来肯定的贴上官网地址, ...

  5. DJango中开启事务的两种方式

    目录 Django中开启事务的两种方式 第一种 第二种 Django中开启事务的两种方式 第一种 from django.db import transaction with transaction. ...

  6. 事务的隔离级别,mysql中开启事务、django中开启事务

    目录 一.事务的特性 二.数据库中开启事务 三.Django中开启事务的两种方式 第一种 第二种 四.事务的隔离级别 隔离级别 如何查看mysql隔离级别? 修改事务的隔离级别 隔离级别解释 read ...

  7. ThinkPHP5.0 实现 app支付宝支付功能

    前几天做项目,要求要用到支付宝接口,第一次做,弄了好几天 各种坑啊,简单写一下我做支付宝支付的过程,希望对也是第一次做支付宝支付的童鞋有帮助, 不懂的可以先去支付平台看一下支付宝支付的文档,我是下的d ...

  8. laravel实现支付宝支付功能

    起因 前段时间因为项目中需要实现支付宝手机网站支付功能,所以写下这篇文章以作记录,不足之处,欢迎指教. 后端框架:Laravel 5.5 业务功能 适用于商家在移动端网页应用中集成支付宝支付功能.商家 ...

  9. SSM 实现支付宝支付功能(图文详解+完整代码)

    阅读本文大概需要 4 分钟. 前言 本教程详细介绍了如何使用ssm框架实现支付宝支付功能.本文章分为两大部分,分别是「支付宝测试环境代码测试」和「将支付宝支付整合到ssm框架」,详细的代码和图文解释, ...

随机推荐

  1. 用python实现九九乘法表输出-两种方法

    2019-08-05 思考过程:九九乘法表需要两层循环,暂且称之为内循环和外循环,因此需要写双层循环来实现. 循环有for和while两种方式. for循环的实现 for i in range(1,1 ...

  2. 洛谷P2125 题解

    吐槽: 只能说这道题很数学,本数学蒟蒻推了半天没推出来,只知道要用绝对值,幸亏教练提醒,才勉强想出正解(似乎不是这样的),真的是很无语. 以上皆为吐槽本题,可直接 跳过 分析: 既然题目是要使书架上的 ...

  3. python_Tensorflow学习(三):TensorFlow学习基础

    一.矩阵的基本操作 import tensorflow as tf   # 1.1矩阵操作 sess = tf.InteractiveSession() x = tf.ones([2, 3], &qu ...

  4. 4、一个打了鸡血的for循环(增强型for循环)

    对于循环,我们大家应该都不陌生,例如do-while循环,while循环,for循环,今天给大家介绍一个有趣的东西——打了鸡血的for循环(增强型for循环). 首先看代码,了解一下for循环的结构: ...

  5. java多线程基础(二)--java线程各状态关系

    注意只有可运行(就绪态)和运行中(运行态)可以相互转换

  6. java并发编程(十四)----(JUC原子类)对象的属性修改类型介绍

    今天我们介绍原子类的最后一个类型--对象的属性修改类型: AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUp ...

  7. bio,nio,aio学习

    http://qindongliang.iteye.com/blog/2018539 1 同步 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 自己上街买衣服,自己亲自干这件事,别的 ...

  8. Springmvc的运行原理 SpringMvc的优点

    SpringMVC框架运行原理 1:客户端发送请求到前端控制器(DispatcherServlet),前端控制器根据请求信息(url),查询一个或多个HandlerMapping, 前端控制器,来决定 ...

  9. 从SpringBoot构建十万博文聊聊缓存穿透

    前言 在博客系统中,为了提升响应速度,加入了 Redis 缓存,把文章主键 ID 作为 key 值去缓存查询,如果不存在对应的 value,就去数据库中查找 .这个时候,如果请求的并发量很大,就会对后 ...

  10. 基于Visual C#的AutoCAD开发——一些网址

    https://blog.csdn.net/xwebsite/article/details/5578446 http://www.cadgj.com/?p=1504