一:事务
使用场景
在电商项目中必备的功能之一用户选择商品进行下单,在用户下单难免会涉及到第三方使用第三方平台进行支付 例如:支付宝
在订单支付过程中肯定会设计到两张表,订单表以及订单信息表,订单表:订单号,订单时间,用户信息等,订单商品表:记录订单中的商品 价格 标题等
在用户下单的时候肯定设计到数据的保存,但是上述两个表其实是有逻辑关联的,只有订单表下单的成功的时候,订单商品表才应该保存到数据库中
因此上述两个表涉及到要么同时成功,要么同时失败,有点需要用到数据库的事务
使用方式
在Django中可以通过django.db.transaction
模块提供的atomic
来定义一个事务,atomic
提供两种用法,
一种是装饰器
from django.db import transaction @transaction.atomic def viewfunc(request): # 这些代码会在一个事务中执行
一种是with语句。
from django.db import transaction def viewfunc(request): # 这部分代码不在事务中,会被Django自动提交 ... with transaction.atomic(): # 这部分代码会在事务中执行
在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以恢复到数据保存点的状态
from django.db import transaction # 创建保存点 save_id = transaction.savepoint() # 回滚到保存点 transaction.savepoint_rollback(save_id) # 提交从保存点到当前状态的所有数据库事务操作 transaction.savepoint_commit(save_id)
二:支付宝
沙箱环境
''' 蚂蚁沙箱环境 (Beta) 是协助开发者进行接口功能开发及主要功能联调的辅助环境。沙箱环境模拟了开放平台部分产品的主要功能和主要逻辑(当前沙箱支持产品请参考“沙箱支持产品列表”)。 在开发者应用上线审核前,开发者可以根据自身需求,先在沙箱环境中了解、组合和调试各种开放接口,进行开发调通工作,从而帮助开发者在应用上线审核完成后,能更快速、更顺利的进行线上调试和验收工作。 如何使用和配置沙箱环境请参考《沙箱环境使用说明》。 注意: 由于沙箱为模拟环境,在沙箱完成接口开发及主要功能调试后,请务必在蚂蚁正式环境进行完整的功能验收测试。所有返回码及业务逻辑以正式环境为准。 为保证沙箱稳定,沙箱环境测试数据会进行定期数据清理。Beta 测试阶段每周日中午12点至每周一中午12点为维护时间。在此时间内沙箱环境部分功能可能会不可用,敬请谅解。 请勿在沙箱进行压力测试,以免触发相应的限流措施,导致无法正常使用沙箱环境。 沙箱支持的各个开放产品,沙箱使用的特别说明请参考各产品的快速接入文档或技术接入文档章节。
https://open.alipay.com/platform/home.htm 详情看文档
'''
签名
作用
生成秘钥文件
openssl OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥 OpenSSL> exit
查看公钥文件
将-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----中间的内容保存在支付宝的用户配置中(沙箱或者正式)
https://openhome.alipay.com/platform/appDaily.htm?tab=info
下载支付宝的公钥文件
将公钥的内容复制保存到一个文本文件中(alipay_pubilc_key.pem),注意需要在文本的首尾添加标记位(-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----) ,形如:
将刚刚生成的私钥app_private_key.pem和支付宝公钥alipay_public_key.pem放到我们的项目目录中
支付流程
三:项目部署
安装
# 从 1.3.0升级上来的用户, 请先卸载pycrypto: pip uninstall pycrypto # 安装python-alipay-sdk pip install python-alipay-sdk --upgrade
使用
设置秘钥
用户商品订单支付代码
class OrderCommitView(View): '''订单创建''' # 开启事务 @transaction.atomic def post(self, request): '''订单创建''' # 判断用户是否登录 user = request.user if not user.is_authenticated(): # 用户未登录 return JsonResponse({'res': 0, 'errmsg': '用户未登录'}) # 接收参数 addr_id = request.POST.get('addr_id') pay_method = request.POST.get('pay_method') sku_ids = request.POST.get('sku_ids') # 获取用户传来的商品id 1,3 # 校验参数 if not all([addr_id, pay_method, sku_ids]): return JsonResponse({'res': 1, 'errmsg': '参数不完整'}) # 校验支付方式 if pay_method not in OrderInfo.PAY_METHODS.keys(): return JsonResponse({'res': 2, 'errmsg': '非法的支付方式'}) # 校验地址 try: addr = Address.objects.get(id=addr_id) except Address.DoesNotExist: # 地址不存在 return JsonResponse({'res': 3, 'errmsg': '地址非法'}) # todo: 创建订单核心业务 # 组织参数 # 订单id: 当前时间+用户id 例如:20171122181630+用户id order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id) # 运费 transit_price = 10 # 总数目和总金额 total_count = 0 total_price = 0 # 设置事务保存点 save_id = transaction.savepoint() try: # todo: 向df_order_info表中添加一条记录 order = OrderInfo.objects.create(order_id=order_id, user=user, addr=addr, pay_method=pay_method, total_count=total_count, total_price=total_price, transit_price=transit_price) # todo: 用户的订单中有几个商品,需要向df_order_goods表中加入几条记录 conn = get_redis_connection('default') cart_key = 'cart_%d' % user.id sku_ids = sku_ids.split(',') for sku_id in sku_ids: # 防止因为未知错误 导致用户不能下单 for i in range(3): # 获取商品的信息 try: sku = GoodsSKU.objects.get(id=sku_id) except: # 商品不存在 返回到事务保存点 transaction.savepoint_rollback(save_id) return JsonResponse({'res': 4, 'errmsg': '商品不存在'}) # 从redis中获取用户所要购买的商品的数量 count = conn.hget(cart_key, sku_id) # todo: 判断商品的库存 if int(count) > sku.stock: # 商品库存不足 返回到事务保存点 transaction.savepoint_rollback(save_id) return JsonResponse({'res': 6, 'errmsg': '商品库存不足'}) # todo: 更新商品的库存和销量 orgin_stock = sku.stock # 获取当前库存 new_stock = orgin_stock - int(count) # 新的库存 等于原始的库存减去用户购买的数量 new_sales = sku.sales + int(count) # 新的销售量 等于原始的购买量加上新的购买的 # update df_goods_sku set stock=new_stock, sales=new_sales # where id=sku_id and stock = orgin_stock # 返回受影响的行数 res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock).update(stock=new_stock, sales=new_sales) if res == 0: if i == 2: # 尝试的第3次用户还没下单成功 transaction.savepoint_rollback(save_id) return JsonResponse({'res': 7, 'errmsg': '下单失败2'}) continue # todo: 向df_order_goods表中添加一条记录 OrderGoods.objects.create(order=order, sku=sku, count=count, price=sku.price) # todo: 累加计算订单商品的总数量和总价格 amount = sku.price * int(count) total_count += int(count) total_price += amount # 跳出循环 break # todo: 更新订单信息表中的商品的总数量和总价格 order.total_count = total_count order.total_price = total_price order.save() except Exception as e: transaction.savepoint_rollback(save_id) return JsonResponse({'res': 7, 'errmsg': '下单失败'}) # 提交事务 transaction.savepoint_commit(save_id) # todo: 清除用户购物车中对应的记录 conn.hdel(cart_key, *sku_ids) # 返回应答 return JsonResponse({'res': 5, 'message': '创建成功'})
Django调用支付宝支付代码
class OrderPayView(View): '''订单支付''' def post(self, request): '''订单支付''' # 用户是否登录 user = request.user if not user.is_authenticated(): return JsonResponse({'res': 0, 'errmsg': '用户未登录'}) # 接收参数 order_id = request.POST.get('order_id') # 校验参数 if not order_id: return JsonResponse({'res': 1, 'errmsg': '无效的订单id'}) try: order = OrderInfo.objects.get(order_id=order_id, user=user, pay_method=3, order_status=1) except OrderInfo.DoesNotExist: return JsonResponse({'res': 2, 'errmsg': '订单错误'}) # 业务处理:使用python sdk调用支付宝的支付接口 # 初始化生成alipay队列 alipay = AliPay( appid="2016090800464054", # 应用id 独有的标识 app_notify_url=None, # 默认异步回调url app_private_key_path=os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem'), # 获取私钥 alipay_public_key_path=os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem'), # 获取公钥 # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, sign_type="RSA2", # RSA 或者 RSA2 debug=True # 默认False ) # 调用支付接口 # 电脑网站支付,需要跳转到https://openapi.alipaydev.com/gateway.do? + order_string total_pay = order.total_price + order.transit_price # Decimal 支付金额 order_string = alipay.api_alipay_trade_page_pay( out_trade_no=order_id, # 订单id total_amount=str(total_pay), # 支付总金额 subject='新鲜果蔬%s' % order_id,# 订单标题 return_url=None, notify_url=None # 可选, 不填则使用默认notify url ) # 返回应答 pay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string return JsonResponse({'res': 3, 'pay_url': pay_url})
查看是否支付成功
class CheckPayView(View): '''查看订单支付的结果''' def post(self, request): '''查询支付结果''' # 用户是否登录 user = request.user if not user.is_authenticated(): return JsonResponse({'res': 0, 'errmsg': '用户未登录'}) # 接收参数 order_id = request.POST.get('order_id') # 校验参数 if not order_id: return JsonResponse({'res': 1, 'errmsg': '无效的订单id'}) try: order = OrderInfo.objects.get(order_id=order_id, user=user, pay_method=3, order_status=1) except OrderInfo.DoesNotExist: return JsonResponse({'res': 2, 'errmsg': '订单错误'}) # 业务处理:使用python sdk调用支付宝的支付接口 # 初始化 alipay = AliPay( appid="2016090800464054", # 应用id app_notify_url=None, # 默认回调url app_private_key_path=os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem'), alipay_public_key_path=os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem'), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, sign_type="RSA2", # RSA 或者 RSA2 debug=True # 默认False ) # 调用支付宝的交易查询接口 while True: response = alipay.api_alipay_trade_query(order_id) # response = { # "trade_no": "2017032121001004070200176844", # 支付宝交易号 # "code": "10000", # 接口调用是否成功 # "invoice_amount": "20.00", # "open_id": "20880072506750308812798160715407", # "fund_bill_list": [ # { # "amount": "20.00", # "fund_channel": "ALIPAYACCOUNT" # } # ], # "buyer_logon_id": "csq***@sandbox.com", # "send_pay_date": "2017-03-21 13:29:17", # "receipt_amount": "20.00", # "out_trade_no": "out_trade_no15", # "buyer_pay_amount": "20.00", # "buyer_user_id": "2088102169481075", # "msg": "Success", # "point_amount": "0.00", # "trade_status": "TRADE_SUCCESS", # 支付结果 # "total_amount": "20.00" # } code = response.get('code') if code == '10000' and response.get('trade_status') == 'TRADE_SUCCESS': # 支付成功 # 获取支付宝交易号 trade_no = response.get('trade_no') # 更新订单状态 order.trade_no = trade_no order.order_status = 4 # 待评价 order.save() # 返回结果 return JsonResponse({'res': 3, 'message': '支付成功'}) elif code == '40004' or (code == '10000' and response.get('trade_status') == 'WAIT_BUYER_PAY'): # 等待买家付款 # 业务处理失败,可能一会就会成功 import time time.sleep(5) continue else: # 支付出错 print(code) return JsonResponse({'res': 4, 'errmsg': '支付失败'})