django实现多种支付、并发订单处理
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)
django实现多种支付、并发订单处理的更多相关文章
- 递归与树的写法-多种支付的设计-支付的接通-celery订单的回退实现
递归与树的写法 data: data=[ {"cat_id":1,"name":"北京","parent_id":0}, ...
- 微信支付 统一订单 $order = WxPayApi::unifiedOrder($input); 断点调试
定位至 CODE /** * 将xml转为array * @param string $xml * @throws WxPayException */ public static function I ...
- 如何在Django模型中管理并发性 orm select_for_update
如何在Django模型中管理并发性 为单用户服务的桌面系统的日子已经过去了 - 网络应用程序现在正在为数百万用户提供服务,许多用户出现了广泛的新问题 - 并发问题. 在本文中,我将介绍在Django模 ...
- Django实现支付宝支付
一 去支付宝申请 - 正式:营业执照 - 测试: 沙箱测试环境 APPID:2016092000554391 买家: esnrce2727@sandbox.com 登录和支付密码: ...
- django 实现电子支付功能
思路:调用第三方支付 API 接口实现支付功能.本来想用支付宝来实现第三方网站的支付功能的,但是在实际操作中发现支付宝没有 Python 接口,网上虽然有他人二次封装的的 Python 接口,但是对我 ...
- Django 对接 支付宝支付, 回调
平台 点击这里进入 蚂蚁金服开放平台 沙箱 点击这里进入 沙箱环境 初始界面 设置公钥 下载创建秘钥工具 1. 进入文档中心 这里 2. 选中 电脑网站支付 3. 进入后选中 API 列表 中的 统 ...
- 利用微信支付的订单查询接口可以在APP 中提高支付的可靠性
最近公司有一个应用,用户可以在微信公众号上面下单,也可以在APP 中下单. 当用户在公共号上面下单时,微信支付成功可以返回微信支付单号,但是在APP 中用户微信支付时,个别时候会出现用户已经付款成功, ...
- python -django 之第三方支付
神魔是第三方支付: 第三方支付是指具有一定实力和信誉保障的第三方独立机构.通过与各大银行签订合同,建立连接用户和银行支付结算系统的平台,从而实现电子支付模式.从另一个角度来看,第三方支付就是非金融机构 ...
- java 高并发 订单编号递增(解决方案)
业务描述: 首先从数据中查找最近的一条订单数据,然后将之前的订单号码+1作为新的订单号码,插入到数据库当中.(需求不能改变) 当出现并发操作时,A从数据库中获取最近一条订单的订单号为N,这是A还没有完 ...
随机推荐
- Filter List Views 筛选器列表视图
In this lesson, you will learn how to filter a List View. Three techniques, based on different scena ...
- LeetCode刷题191126
博主渣渣一枚,刷刷leetcode给自己瞅瞅,大神们由更好方法还望不吝赐教.题目及解法来自于力扣(LeetCode),传送门. 今天状态不好,划水第二天. 算法: 题号:20 给定一个只包括 '(', ...
- Nginx代理前端代码
Nginx 安装配置 Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/ ...
- Navicat Premium 连接oracle 提示ORA-01017:用户名/口令无效;登陆被拒绝
Navicat Premium 连接oracle,密码明明是对的,还是提示 ORA-01017:用户名/口令无效:登陆被拒绝.而用Pl/SQL 连接没有问题. 其实用户名和密码是对的,但还是会报错,这 ...
- Test Case:: 12C ASM New feature (Doc ID 1571975.1)
Test Case:: 12C ASM New feature (Doc ID 1571975.1) APPLIES TO: Oracle Database - Enterprise Edition ...
- MySQL数据库文件的移动和权限设置
新型数据库层出不穷,MySQL一幅日薄西山的样子.其实还有很多人或者偏爱.或者使用以前遗留的系统,仍然生活在MySQL的世界. 我也是有很久不用了,这个很久超过十年. 不过前几天有个朋友让我帮忙为他们 ...
- springboot模板
1.thymeleaf模板 2.Freemarker模板 Thymeleaf模板 首先导入依赖 <dependency> <groupId>org.springframewor ...
- tomcat在centos7能启动不显示
首先查看启动日志,日志显示成功启动,java路径也对,没有问题. 日志目录路径为$(tomcat)/logs/catalina.log 查看命令为:tail -300f catalina.log 然后 ...
- Python集合类型的操作与应用
Python集合类型的操作与应用 一.Python集合类型 Python中的集合类型是一个包含0个或多个数据项的无序的.不重复的数据组合,其中,元素类型只能是固定数据类型,如整数.浮点数.字符串.元组 ...
- ubuntu下面安装nodejs
对于刚接触ubuntu的同学来说,一切都是新的,一切都是那么熟悉而又不熟悉的.不管是作为一个前端工程师还是一个后端工程师,我相信大家知道nodejs,但是如果希望自己能够在ubuntu上面使用node ...