django中使用事務(wù)及接入支付寶支付功能
之前一直想記錄一下在項(xiàng)目中使用到的事務(wù)以及支付寶支付功能,自己一直犯懶沒有完,趁今天有點(diǎn)興致,在這記錄一下。
商城項(xiàng)目必備的就是支付訂單的功能,所以就會涉及到訂單的保存以及支付接口的引入。先來看看訂單的保存,在數(shù)據(jù)庫模型涉及之初,將訂單分成了兩個表,一個為訂單表,記錄訂單的基本信息,如訂單號,用戶信息,運(yùn)費(fèi)之類,一個為訂單商品表,記錄該訂單中的商品信息。在保存訂單時,肯定會涉及到兩個表的新建和保存,其實(shí)還有一張表也需要進(jìn)行一些修改,那就是商品表,當(dāng)一個訂單保存成功,意味著本次交易成功,商品售出,商品的庫存應(yīng)該進(jìn)行修改。所以,在保存訂單這一操作中,涉及到的表有三張。所以在保存訂單時,多表數(shù)據(jù)的修改,要嘛同時成功,要嘛同時失敗,這就跟數(shù)據(jù)庫中的事務(wù)很像,因此,在這里引入事務(wù),來完成訂單保存的功能。
在Django中可以通過 django.db.transaction
模塊提供的 atomic 來定義一個事務(wù), atomic 提供兩種用法,一種是裝飾器,一種是with語句。
from django.db import transaction @transaction.atomic def viewfunc(request): # 這些代碼會在一個事務(wù)中執(zhí)行 ... from django.db import transaction def viewfunc(request): # 這部分代碼不在事務(wù)中,會被Django自動提交 ... with transaction.atomic(): # 這部分代碼會在事務(wù)中執(zhí)行 ...
在Django中,還提供了保存點(diǎn)的支持,可以在事務(wù)中創(chuàng)建保存點(diǎn)來記錄數(shù)據(jù)的特定狀態(tài),數(shù)據(jù)庫出現(xiàn)錯誤時,可以恢復(fù)到數(shù)據(jù)保存點(diǎn)的狀態(tài)
from django.db import transaction # 創(chuàng)建保存點(diǎn) save_id = transaction.savepoint() # 回滾到保存點(diǎn) transaction.savepoint_rollback(save_id) # 提交從保存點(diǎn)到當(dāng)前狀態(tài)的所有數(shù)據(jù)庫事務(wù)操作 transaction.savepoint_commit(save_id)
所以,可以在序列化器的create方法中,創(chuàng)建一個事務(wù),還進(jìn)行數(shù)據(jù)的修改保存還有新建,若有地方出錯,則直接回滾,若沒有問題則提交事務(wù)。代碼如下
def create(self, validated_data): """ 保存訂單 """ # 獲取當(dāng)前下單用戶 user = self.context['request'].user # 組織訂單編號 20170903153611+user.id # timezone.now() -> datetime order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) address = validated_data['address'] pay_method = validated_data['pay_method'] # 生成訂單 with transaction.atomic(): # 創(chuàng)建一個保存點(diǎn) save_id = transaction.savepoint() try: # 創(chuàng)建訂單信息 order = OrderInfo.objects.create( order_id=order_id, user=user, address=address, total_count=0, total_amount=Decimal(0), freight=Decimal(10), pay_method=pay_method, status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID'] ) # 獲取購物車信息 redis_conn = get_redis_connection("cart") redis_cart = redis_conn.hgetall("cart_%s" % user.id) cart_selected = redis_conn.smembers('cart_selected_%s' % user.id) # 將bytes類型轉(zhuǎn)換為int類型 cart = {} for sku_id in cart_selected: cart[int(sku_id)] = int(redis_cart[sku_id]) # # 一次查詢出所有商品數(shù)據(jù) # skus = SKU.objects.filter(id__in=cart.keys()) # 處理訂單商品 sku_id_list = cart.keys() for sku_id in sku_id_list: while True: sku = SKU.objects.get(id=sku_id) sku_count = cart[sku.id] # 判斷庫存 origin_stock = sku.stock # 原始庫存 origin_sales = sku.sales # 原始銷量 if sku_count > origin_stock: transaction.savepoint_rollback(save_id) raise serializers.ValidationError('商品庫存不足') # 用于演示并發(fā)下單 # import time # time.sleep(5) # 減少庫存 # sku.stock -= sku_count # sku.sales += sku_count # sku.save() new_stock = origin_stock - sku_count new_sales = origin_sales + sku_count # 根據(jù)原始庫存條件更新,返回更新的條目數(shù),樂觀鎖 ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales) if ret == 0: continue # 累計(jì)商品的SPU 銷量信息 sku.goods.sales += sku_count sku.goods.save() # 累計(jì)訂單基本信息的數(shù)據(jù) order.total_count += sku_count # 累計(jì)總金額 order.total_amount += (sku.price * sku_count) # 累計(jì)總額 # 保存訂單商品 OrderGoods.objects.create( order=order, sku=sku, count=sku_count, price=sku.price, ) # 更新成功 break # 更新訂單的金額數(shù)量信息 order.total_amount += order.freight order.save() except serializers.ValidationError: raise except Exception as e: logger.error(e) transaction.savepoint_rollback(save_id) raise # 提交事務(wù) transaction.savepoint_commit(save_id) # 更新redis中保存的購物車數(shù)據(jù) pl = redis_conn.pipeline() pl.hdel('cart_%s' % user.id, *cart_selected) pl.srem('cart_selected_%s' % user.id, *cart_selected) pl.execute() return order
還有一點(diǎn)需要注意的是,當(dāng)訂單提交,購物車中相應(yīng)的商品應(yīng)該進(jìn)行刪除。好了,以上就是django中的事務(wù)。
再來說說支付寶支付功能的引入,現(xiàn)在基本上所有的項(xiàng)目涉及到支付功能時都會引入第三方支付,其中使用最廣泛的應(yīng)該就是支付寶和微信了,這里我使用的是支付寶支付。當(dāng)訂單創(chuàng)建完成,接下來就是支付了。
先來縷一下流程,用戶點(diǎn)擊按鈕請求支付寶支付界面,先進(jìn)行登錄,登錄成功后進(jìn)行支付操作,支付成功會進(jìn)行回調(diào)。
首先第一步,用戶點(diǎn)擊按鈕,后端會進(jìn)行url的拼接,將拼接好的url返給前端,前端進(jìn)行跳轉(zhuǎn),跳轉(zhuǎn)到支付寶相關(guān)界面,用戶進(jìn)行登錄和支付等操作。
查看pythonsdk,首先我們可以通過openssl命令生成一個密鑰(公鑰和私鑰),私鑰自己留存,公鑰用戶校驗(yàn)。命令如下:
openssl
OpenSSL> genrsa -out app_private_key.pem 2048 # 私鑰 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 導(dǎo)出公鑰 OpenSSL> exit
同時,你要從支付寶獲得一個公鑰字符串,格式可參考:https://github.com/fzlee/alipay/blob/master/tests/certs/ali/ali_public_key.pem
由于上面我們用的是RSA生成的密鑰,所以在支付寶中我們也需要RSA的公鑰
設(shè)置好了密鑰,我們就可以開始寫視圖,代碼如下:
class PaymentView(APIView): """ 支付 """ permission_classes = (IsAuthenticated,) def get(self, request, order_id): """ 獲取支付鏈接 """ # 判斷訂單信息是否正確 try: order = OrderInfo.objects.get(order_id=order_id, user=request.user, pay_method=OrderInfo.PAY_METHODS_ENUM["ALIPAY"], status=OrderInfo.ORDER_STATUS_ENUM["UNPAID"]) except OrderInfo.DoesNotExist: return Response({'message': '訂單信息有誤'}, status=status.HTTP_400_BAD_REQUEST) # 構(gòu)造支付寶支付鏈接地址 alipay = AliPay( appid=settings.ALIPAY_APPID, app_notify_url=None, # 默認(rèn)回調(diào)url app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"), alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem"), # 支付寶的公鑰,驗(yàn)證支付寶回傳消息使用,不是你自己的公鑰, sign_type="RSA2", # RSA 或者 RSA2 debug=settings.ALIPAY_DEBUG # 默認(rèn)False ) order_string = alipay.api_alipay_trade_page_pay( out_trade_no=order_id, total_amount=str(order.total_amount), subject="美多商城%s" % order_id, return_url="http://www.meiduo.site:8080/pay_success.html", ) # 需要跳轉(zhuǎn)到https://openapi.alipay.com/gateway.do? + order_string # 拼接鏈接返回前端 alipay_url = settings.ALIPAY_URL + "?" + order_string return Response({'alipay_url': alipay_url})
相關(guān)的參數(shù)可以提前在配置文件中配置好(ALPAY_APPID,ALPAY_URL,ALPAY_DEBUG)注意ALPAY為True時才啟用沙箱環(huán)境。當(dāng)用戶支付成功,會對你填寫的回調(diào)網(wǎng)址進(jìn)行回調(diào),返回的參數(shù)如下圖。
前端頁面將此數(shù)據(jù)發(fā)送給后端,后端檢驗(yàn)并保存支付結(jié)果。以上就是全部過程。具體的過程可以參考pythonsdk。
總結(jié)
以上所述是小編給大家介紹的django中使用事務(wù)及接入支付寶支付功能,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大家的!
相關(guān)文章
實(shí)現(xiàn)python版本的按任意鍵繼續(xù)/退出
本文給大家簡單介紹了在windows以及l(fā)inux下實(shí)現(xiàn)python版本的按任意鍵繼續(xù)/退出功能,非常的簡單實(shí)用,linux下稍微復(fù)雜些,有需要的小伙伴可以參考下2016-09-09python對list中的每個元素進(jìn)行某種操作的方法
今天小編就為大家分享一篇python對list中的每個元素進(jìn)行某種操作的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06opencv函數(shù)threshold、adaptiveThreshold、Otsu二值化的實(shí)現(xiàn)
這篇文章主要介紹了opencv函數(shù)threshold、adaptiveThreshold、Otsu二值化的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Python教程使用Chord包實(shí)現(xiàn)炫彩弦圖示例
在可視化中,有時候會使用到弦圖(Chord Diagram)來表示事物之間關(guān)系,本篇文章教大家如何使用Chord包實(shí)現(xiàn)炫彩弦圖,有需要的朋友可以借鑒參考下,希望大家多多進(jìn)步,早日升職加薪2021-09-09Python使用watchfiles實(shí)現(xiàn)監(jiān)控目錄變更
在工作中難免會碰到這樣的需求,監(jiān)控指定目錄,下面小編就來和大家介紹一下如何利用watchfiles 模塊實(shí)現(xiàn)監(jiān)控目錄的變更,感興趣的可以了解下2023-09-09Python使用wget實(shí)現(xiàn)下載網(wǎng)絡(luò)文件功能示例
這篇文章主要介紹了Python使用wget實(shí)現(xiàn)下載網(wǎng)絡(luò)文件功能,簡單介紹了wget安裝以及Python使用wget下載tar格式網(wǎng)絡(luò)文件并進(jìn)行解壓處理相關(guān)操作技巧,需要的朋友可以參考下2018-05-05