django多种支付、并发订单处理实例代码


Posted in Python onDecember 13, 2019

django实现多种支付方式

'''
#思路
  
  我们希望,通过插拔的方式来实现多方式登录,比如新增一种支付方式,那么只要在项目中新增一个py文件,导入里面的pay方法就可以了,这样在支付业务中支付语句是不发生变化的。
  所以就可以使用python的鸭子类型及面向对象的反射方法来实现功能

'''

##新建一个Pay文件夹,里面放支付方式.py文件
#Alipay.py
class Alipay:
  def pay(self):
    pass
#Visapay.py
class Visapay:
  def pay(self):
    pass
#Wxpay.py(完全按照接口文档来得)
import time
#记得导入商户号和key哦!
from app01.wx import settings
class Wxpay:
  def pay(self,order_data):
    self.order_id = order_data["order_id"]
    self.open_id = order_data['open_id']
    self.ip = order_data['ip']
    data_body = self.get_body_data()
    import requests
    url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
    response = requests.post(url, data_body.encode("utf-8"), headers={'content-type': "application/xml"})
    res_dict = self.xml_to_dic(response.content)
    timeStamp = str(int(time.time()))
    paySign = self.get_pay_sign(res_dict, timeStamp)

    data_dic = {
      'timeStamp': timeStamp,
      'nonceStr': res_dict['nonce_str'],
      'package': f"prepay_id={res_dict['prepay_id']}",
      'signType': 'MD5',
      "paySign": paySign,
    }

    return data_dic

  def get_pay_sign(self, res_dict, timeStamp):
    print("res_dict", res_dict)
    data_dic = {
      'appId': res_dict['appid'],
      'timeStamp': timeStamp,
      'nonceStr': res_dict['nonce_str'],
      'package': f"prepay_id={res_dict['prepay_id']}",
      "signType": "MD5"
    }
    sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])
    sign_str = f"{sign_str}&key={settings.pay_apikey}"
    import hashlib
    md5 = hashlib.md5()
    md5.update(sign_str.encode("utf-8"))
    sign = md5.hexdigest()
    return sign.upper()

  def xml_to_dic(self, xml_data):
    import xml.etree.ElementTree as ET
    '''
    xml to dict
    :param xml_data:
    :return:
    '''
    xml_dict = {}
    root = ET.fromstring(xml_data)
    for child in root:
      xml_dict[child.tag] = child.text
    return xml_dict

  def get_random(self):
    import random
    data = "123456789zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP"
    nonce_str = "".join(random.sample(data, 30))
    return nonce_str



  def get_sign(self):
    data_dic = {
      "nonce_str": self.nonce_str,
      "out_trade_no": self.out_trade_no,
      "spbill_create_ip": self.spbill_create_ip,
      "notify_url": self.notify_url,
      "openid": self.open_id,
      "body": self.body,
      "trade_type": "JSAPI",
      "appid": self.appid,
      "total_fee": "1",
      "mch_id": self.mch_id
    }

    sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])
    sign_str = f"{sign_str}&key={settings.pay_apikey}"
    import hashlib
    md5 = hashlib.md5()
    md5.update(sign_str.encode("utf-8"))
    sign = md5.hexdigest()
    return sign.upper()

  def get_body_data(self):
    self.appid = settings.AppId
    # openid=self.open_id
    self.mch_id = str(settings.pay_mchid)
    self.nonce_str = self.get_random()
    self.out_trade_no = self.order_id
    self.spbill_create_ip = self.ip
    self.notify_url = "https://www.test.com"
    self.body = "老男孩学费"
    self.sign = self.get_sign()
    body_data = f"""
      <xml>
        <appid>{self.appid}</appid>
        <mch_id>{self.mch_id}</mch_id>
        <nonce_str>{self.nonce_str}</nonce_str>
        <sign>{self.sign}</sign>
        <body>{self.body}</body>
        <out_trade_no>{self.out_trade_no}</out_trade_no>
        <total_fee>1</total_fee>
        <spbill_create_ip>{ self.spbill_create_ip}</spbill_create_ip>
        <notify_url>{self.notify_url}</notify_url>
        <openid>{self.open_id}</openid>
        <trade_type>JSAPI</trade_type> 
      </xml>"""
    return body_data
  
  
  
##调用支付方法的语句(一般支付都是发生在订单创建好之后)
import importlib
from rest_framework.response import Response
pay_method = "Wxpay" #这里是举例子,所以把pay_method写死了,正常情况下,应该是外面传来的支付方式,然后用pay_method接收
try:
  #用字符串导入支付方式的py文件,例如这里的app01.Pay.{pay_method}
  pay_field = importlib.import_module(f"app01.Pay.{pay_method}")
  
  #用反射拿到该文件下面的类
  pay_method_class = getattr(pay_field, pay_method)
except:
  return Response({"code": 205, "msg": "错误的支付方式"})

order_data['ip'] = host_ip
order_data['open_id'] = open_id

#完成支付,并把支付数据返回
pay_data = pay_method_class().pay(order_data)
'''
这里直接用反射拿到的支付类,然后使用它的pay方法,完成支付
'''
return Response({"code": 200, "msg": "ok", "data": pay_data})

django实现订单创建及支付

'''
几个注意点:
  1.a,b两个用户同时买一个库存为1的商品,这样为了保证数据安全(即a买了,库存没更新,b又买了,这样就存在安全问题),需要在数据库操作时加锁,有两个选择:悲观锁和乐观锁。
  悲观锁:冲突比较多的时候,使用悲观锁。悲观锁获取数据时对数据行了锁定,其他事务要想获取锁,必须等原事务结束。
  乐观锁:冲突比较少的时候,使用乐观锁。查询时不锁数据,提交更改时进行判断.使用乐观锁前,要先 设置mysql事务的隔离级别transaction-isolation = READ-COMMITTED
  2.a用户的订单有两种商品,这样提交订单后,假如第一种成功,第二种失败,很显然在订单一个函数里面写一个逻辑是行不通的,应该要么同时成功,要么同时失败。于是自然而然就用到了事务。
'''
#urls.py
from django.urls import path
from app01.view import order
urlpattern = [
  path('order/create', order.Create.as_view()),
]


#common文件夹下的func.py文件
import time,random
def get_order_id():
  str_all="1242356796734534"
  return time.strftime("%Y%m%d%H%M%S")+"".join(random.sample(str_all,5))


#view文件夹下的order.py文件
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from django import forms
from django.core.cache import cache
from common import func

class OrderForm():
  phone = forms.CharField(
    error_message = {
      'required': "手机号不能为空"
    },
       # 调用Form组件中的验证器来校验手机号
    # validators=[RegexValidator(r'1[1-9][0-9]{9}', '手机号格式不正确')], 
  )
  
    token = forms.CharField( error_messages={
      "required": "token不能为空"
    })
  province=forms.CharField( error_messages={
      "required": "省份不能为空"
    })
  city = forms.CharField(error_messages={
    "required": "城市不能为空"
  })
  county = forms.CharField(error_messages={
    "required": "县/区不能为空"
  })
  address = forms.CharField(error_messages={
    "required": "详细地址不能为空"
  })
  name = forms.CharField(error_messages={
    "required": "姓名不能为空"
  })
  
  
  
  
class Create(APIView):
  @transaction.atomic
  def post(self, requset):
    param = request.data
    #form表单检验订单数据是否符合规范
    for_obj = OrderForm(param)
    #校验成功,并且买东西了
    if for_obj.is_valid() and param.get('buy_list'):
       buy_list = param.get("buy_list")
        #固定方法拿到付款用户的id
        if request.META.get("HTTP_X_FORWARDED_FOR"):
          host_ip = request.META["HTTP_X_FROWARDED_FOR"]
        else:
          host_ip = request.META["REMOTE_ADDR"]
        #校验token,保证登入状态
        cache_data = cache.get(param["token"])
        if not cache_data:
          return Response({"code": 202, "msg": "错误的token"})
        openid = cache_data.split("&")[0]
        #通过openid查找用户
        user_data = models.Wxuser.objects.filter(openid=openid).first()
        
        #组织部分总订单数据
      
        order_data = {"consignee_mobile": param['phone'],
               'consignee_name': param['name'],
               'wxuser_id': user_data.id,
               "memo": param['remark'],
               "consignee_area": f"{param['province']},{param['city']},{param['county']}",
               "consignee_address": param['address'],
               }
        
        order_data['order_id']=func.get_order_id()
        order_data["order_total"]=0
        
        
        #获取用户购买商品的id
        all_product_id=list(buy_list.keys())
        #通过id来获取商品的信息
        product_data=models.Product.objects.filter(product_id__in=all_product_id).all()
        
        
        #开启事务
 sid = transaction.savepoint()
        for product in product_data:
          product.product_id=str(product.product_id)
          # num=buy_list[product.id]
          #获取商品的库存
          for i in range(3):
            product_stock = product.stock.quantity
            new_product_stock = product_stock-buy_list[product.product_id]
            if new_product_stock<0:
              transaction.savepoint_rollback(sid)
              return Response({"code": 204, "msg": f"{product.name}库存不足"})
            #乐观锁
            res=models.Stock.objects.filter(quantity=product_stock,stock_id=product.stock.stock_id).update(quantity=new_product_stock)
            if not res :
              #如果两次都不成功,就让用户重新下单
              if i==2:
                transaction.savepoint_rollback(sid)
                return Response({"code": 205, "msg": "下单失败从新下单"})
              else:
                continue
            else:
              break

          #组织子订单数据
          order_item_data = {'order_id': order_data['order_id'], 'product_id': product.product_id,"name": product.name, "image": product.image, "price": product.price,
                    "nums": buy_list[product.product_id], "brief": product.brief}
          #创建数据
          models.Order_items.objects.create(**order_item_data)

          #获取订单总金额
          order_data["order_total"] += buy_list[product.product_id]*product.price

        #创建总订单
        models.Order.objects.create(**order_data)

        transaction.savepoint_commit(sid)

        #提交延时任务,判断订单再指定时间内,有没有支付,如果没有支付,回滚库存,取消订单
        func.check_order(order_data['order_id'])

        #如果pay_methon是外面传的
        pay_methon= "Wxpay"
        try:
          #导入app01.Pay.{pay_methon}
          pay_filed=importlib.import_module(f"app01.Pay.{pay_methon}")

          #用反射获取,这个文件中的类
          pay_methon_class=getattr(pay_filed,pay_methon)
        except:
          return Response({"code": 205, "msg": "错误的支付方式"})

        order_data['ip'] = host_ip
        order_data['open_id'] = openid

        #获取小程序所需的支付数据
        pay_data= pay_methon_class().pay(order_data)

        # pay_methon ="Alipay"
        # if pay_methon=="Wxpay":
        #   Wxpay().pay
        # elif:...
        # pay_methon
        return Response({"code": 200, "msg": "ok","data":pay_data})
    else:
      return Response({"code": 203, "msg": "缺少参数"})
    
    
class Notify(APIView):
  def post(self,request,paymethon):
    pay_filed = importlib.import_module(f"app01.Pay.{paymethon}")
    pay_methon_class = getattr(pay_filed, paymethon)
    data=pay_methon_class().notify(request.data)
    if data['stauts']=="suc":
      models.Order.objects.filter(order_id=data['order_id']).update(pay_status="")

celery实现库存回滚

#proj_celery文件夹下的celery.py文件
import celery
import time

# broker='redis://127.0.0.1:6379/2' 不加密码
backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/2'
cel = celery.Celery('test', backend=backend, broker=broker)

import os, sys
import django

BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # 定位到你的django根目录
# sys.path.append(os.path.join(BASE_DIR, "app01"))
sys.path.append(os.path.abspath(BASE_DIR))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
django.setup()
from django.db import transaction


@cel.task
@transaction.atomic
def del_order(order_id):

  from app01 import models

  # 查看订单数据
  order_data = models.Order.objects.filter(order_id=order_id, pay_status=False).first()

  # 如果有数据表示没有支付,要进行库存回滚,和取消订单
  if order_data:

    # 获取该订单下的所有子订单
    order_items = models.Order_items.objects.filter(order_id=order_id).all()

    # 将子订单中的数据转变成 {商品id:购买数量,。。。}的格式
    product_all_dic = {item.product_id: item.nums for item in order_items}

    # 获取所有商品的id,成为list格式
    product_all_id = list(product_all_dic.keys())

    # 获取所有的商品
    all_product = models.Product.objects.filter(product_id__in=product_all_id).all()

    sid = transaction.savepoint()

    # 把对应的商品进行库存回滚
    for product in all_product:
      for i in range(3):
        stock = product.stock.quantity
        new_stock = stock + product_all_dic[product.product_id]

        #乐观锁
        res = models.Stock.objects.filter(stock_id=product.stock.stock_id, quantity=stock).update(
          quantity=new_stock)

        if not res:
          if i == 2:
            transaction.savepoint_rollback(sid)

            # 如果这个执行失败了,那我们要从新提交任务,不然库存无法回滚
            from app01.common import func
            func.check_order(order_id, 1)
            return
          else:
            continue
        else:
          break
    # 修改订单状态
    res1 = models.Order.objects.filter(order_id=order_id, pay_status=False).update(status="dead")
    if res1:
      transaction.savepoint_commit(sid)
    else:
      transaction.savepoint_rollback(sid)

悲观锁和乐观锁

悲观锁和乐观锁与并发订单处理

以上就是本次介绍的全部相关知识点,感谢大家的学习和对三水点靠木的支持。

Python 相关文章推荐
python使用正则搜索字符串或文件中的浮点数代码实例
Jul 11 Python
用Python编写一个简单的俄罗斯方块游戏的教程
Apr 03 Python
Python的动态重新封装的教程
Apr 11 Python
详解详解Python中writelines()方法的使用
May 25 Python
Python实时获取cmd的输出
Dec 13 Python
python 编码规范整理
May 05 Python
深入解析神经网络从原理到实现
Jul 26 Python
python__new__内置静态方法使用解析
Jan 07 Python
Python任务自动化工具tox使用教程
Mar 17 Python
详解Python中pyautogui库的最全使用方法
Apr 01 Python
python 还原梯度下降算法实现一维线性回归
Oct 22 Python
Jupyter Notebook 如何修改字体和大小以及更改字体样式
Jun 03 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
Dec 12 #Python
Python+OpenCV 实现图片无损旋转90°且无黑边
Dec 12 #Python
使用python去除图片白色像素的实例
Dec 12 #Python
用Python去除图像的黑色或白色背景实例
Dec 12 #Python
python 实现将小图片放到另一个较大的白色或黑色背景图片中
Dec 12 #Python
flask的orm框架SQLAlchemy查询实现解析
Dec 12 #Python
python实现批量处理将图片粘贴到另一张图片上并保存
Dec 12 #Python
You might like
Windows下的PHP5.0详解
2006/11/18 PHP
json的键名为数字时的调用方式(示例代码)
2013/11/15 PHP
thinkphp判断访客为手机端或PC端的方法
2014/11/24 PHP
基于JQuery实现CheckBox全选全不选
2011/06/27 Javascript
json的定义、标准格式及json字符串检验
2014/05/11 Javascript
JavaScript实现常用二级省市级联下拉列表的方法
2015/03/25 Javascript
纯javascript模仿微信打飞机小游戏
2015/08/20 Javascript
五种js判断是否为整数类型方式
2015/12/03 Javascript
jQuery实现简单的点赞效果
2020/05/29 Javascript
jquery自适应布局的简单实例
2016/05/28 Javascript
js简单实现调整网页字体大小的方法
2016/07/23 Javascript
jQuery EasyUI Tab 选项卡问题小结
2016/08/16 Javascript
用JS动态设置CSS样式常见方法小结(推荐)
2016/11/10 Javascript
原生js实现查询天气小应用
2016/12/09 Javascript
Js利用console计算代码运行时间的方法示例
2017/09/24 Javascript
Vue.directive使用注意(小结)
2018/08/31 Javascript
如何在 JavaScript 中更好地利用数组
2018/09/27 Javascript
微信小程序实现按字母排列选择城市功能
2019/11/25 Javascript
[00:59]DOTA2英雄背景故事——上古巨神
2020/06/28 DOTA
python 数据的清理行为实例详解
2017/07/12 Python
Python内置函数delattr的具体用法
2017/11/23 Python
TensorFlow深度学习之卷积神经网络CNN
2018/03/09 Python
Windows 安装 Anaconda3+PyCharm的方法步骤
2019/06/13 Python
安装docker-compose的两种最简方法
2019/07/30 Python
使用python将最新的测试报告以附件的形式发到指定邮箱
2019/09/20 Python
Python如何使用PIL Image制作GIF图片
2020/05/16 Python
python3爬虫中异步协程的用法
2020/07/10 Python
python logging模块的使用详解
2020/10/23 Python
管理部部长岗位职责
2013/12/05 职场文书
工程造价专业大学生职业生涯规划书
2014/01/18 职场文书
地球一小时倡议书
2014/04/15 职场文书
家庭财产分割协议范文
2014/11/24 职场文书
求职简历自我评价怎么写
2015/03/10 职场文书
2019年度政务公开考核工作总结模板
2019/11/11 职场文书
MySQL完整性约束的定义与实例教程
2021/05/30 MySQL
动画《新网球王子 U-17 WORLD CUP》希腊队PV公开
2022/04/02 日漫