Django 2.0.2 (Django-rest-framework) and the front-end and back-end separated mall website developed by front-end vue
Online demo address: http://vueshop.mtianyan.cn/
github source code address: https://github.com/mtianyan/VueDjangoFrameWorkShop
This section: Interpretation of Alipay payment source code
Interpretation of Alipay payment source code
There are some parameters for init.
def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): # Appid is our appid or sandbox environment appid self.appid = appid # self.app_notify_url = app_notify_url # The path of our private key file self.app_private_key_path = app_private_key_path # We're going to read the file and generate private_key. self.app_private_key = None # self.return_url = return_url # Open the file, read it, and read it. # Call import_key of RSA (from Crypto. PublicKey import RSA) with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) # Alipay's public key is practically useless in link generation. # But when it comes to validating Alipay's message to us, it is useful. self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.import_key(fp.read()) # debug is the gateway that true calls the sandbox if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do"
direct_pay method
# Pass in some parameters. Transaction title, our order number, total amount. def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data)
biz_content is an important parameter. Four required fields related to the order.
Variable parameters are passed in through python. If there are other values behind, just pass in.
When biz_content is generated, the build_body method is called
def build_body(self, method, biz_content, return_url=None): # The fields in data are consistent with the required fields for public requests data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data
There are two kinds of parameters: one is the public request parameter, the other is the request parameter (business).
The main parameter is the common request parameter, which is nested with bizcontent.
build_body is the public request parameter (including biz_content)
If the parameter passed in has return_url, we put return_url in data as well.
Builbody is used to generate the entire message format.
- Get data in message format
Sign the message format
def sign_data(self, data): data.pop("sign", None) # Sorted strings unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # Get the final order information string signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string
Signature is crucial. Before signing, pop out the sign field. (If so, of course not now)
Data is a data that clears unnecessary parameters
Sort data. Parameters are passed in for sorting
def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # dump out the dictionary type data for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) //Sort data. Parameters are passed in to sort return sorted([(k, v) for k, v in data.items()])
Sort the data and generate a tuple. This is a sequential tuple
unsigned_items is already an array of sorted arrays.
app_id=sandbox appid&biz_content={"subject":"\u6d4b\u8bd5\u8ba2\u5355","out_trade_no":"20180312mtianyan001","total_amount":9999,"product_code":"FAST_INSTANT_TRADE_PAY"}&charset=utf-8&method=alipay.trade.page.pay¬ify_url=http://127.0.0.1:8000/alipay/return/&return_url=http://127.0.0.1:8000/alipay/return/&sign_type=RSA2×tamp=2018-03-12 18:59:09&version=1.0
unsigned_string is a concatenated string.
Then we're going to sign with this string.
sign = self.sign(unsigned_string.encode("utf-8")) def sign(self, unsigned_string): # Start calculating signatures key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 encoding, converted to unicode representation and removed carriage return sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign
First, we need to get our private key, and then use PKCS1_v1_5 to sign.
from Crypto.Signature import PKCS1_v1_5
Generate a signed object signer and call the sign function of the object. Generate a signature using SHA256 algorithm
The generation of signatures is not complete, and the document shows that the signature has the last step. And base64 coding
from base64 import decodebytes, encodebytes
What we use here is the encodebytes encoding of base64. After encoding, we use decode to convert it to utf-8 string.
Signature string completion.
After getting the value of sign
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
quote_plus handles the url to a certain extent. The difference is that parameter signatures are not allowed to carry excess characters such as http://etc.
That is to say, the original string is used for signature, and our url request is to add sign parameter to the string after quote.
The final return value is received by the url. Stitching up our call sandbox interface URL to form payment page links
We can also pay for this order.
Alipay will jump back to our own page after the payment is successful. That's because we didn't pass him our return_url.
Payment completed immediately after jumping back to the page.
The paid order number will be prompted to be paid if it is generated again.
It becomes the url we pass in the past with a series of parameters. - That's what our return url does.
So the question is, what does notify url mean?
The user scanned the code but did not pay, and then closed the payment page. If the user pays in the bills in his cell phone
Instead of paying on this page, the return url is useless.
return url is the page that Alipay will jump automatically after we pay successfully.
Notfy URL is when the user completes the payment (not on the current payment page, but through a mobile phone bill, etc.)
Alipay will initiate an asynchronous request for you.
Asynchronous requests are like when we close the page, Alipay sends a notification that the bill has been paid by the user. Need to go to the system to change the status of the order ah wait for some follow-up work.
At that time, url had an asynchronous interaction with Alipay. It is impossible to return the page to the browser.
So we also need an asynchronous receiving interface. return is an interface for synchronous reception.
Alipay notifications interface verification
More detailed description of return url and notify url
In the last lesson, we have been able to generate pages for Alipay payment.
This is a request from Alipay. We need to verify whether the data returned by Alipay is valid.
Because this data may be intercepted by others. It modifies the data inside to make the order status a successful payment.
Verify that the data is correct. The encryption process with generation is just an inverse process.
def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # Sorted strings unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature)
Put the sign pop inside, then sign the rest, and compare the signed string with the original one to see if it is legal.
return_url = 'http://127.0.0.1:8000/?total_amount=100.00×tamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0' o = urlparse(return_url) query = parse_qs(o.query) processed_query = {} ali_sign = query.pop("sign")[0]
Make sure you pop it and delete the sign inside.
def _verify(self, raw_content, signature): # Start calculating signatures key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False
The string is compared with the sign returned by Alipay.
After verification, we have to modify the interface of Alipay's order status.
Write an interface, Alipayviewset
django integrates Alipay notify_url and return_url interface -1
Whether payment is synchronous or asynchronous, we are asked to post notify url.
return url is a non required field. It will only tell us the success of the payment on Alipay page.
Asynchronous notification of payment results.
https://docs.open.alipay.com/270/105902/
For the transaction paid by the PC website, Alipay will notify the merchant system of the result of payment as a parameter in the form of POST request, after the user's payment is completed, according to the notify_url introduced by the merchant in API.
Page bounce parameter.
return_url is a mechanism for synchronous return: get requests to our server are made
Write a view that handles both post and get. You only need to configure a url to complete the adaptation of two forms of returns
Because our Alipay related part is not model. So we can use the bottom-level api view to do this
from rest_framework.views import APIView class AlipayView(APIView):
- Define get and post functions to handle return url and notify url, respectively
def get(self, request): """ Deal with Alipay's return_url return """ def post(self, request): """ Handling Alipay's notify_url """
Go to url to configure an interface address
from trade.views import AlipayView # Alipay payment related interface path('alipay/return/', AlipayView.as_view())
Processing asynchronous requests first.
First verify whether Alipay post requests come in.
Change the return url and notifyurl in Alipay.py in our utils to online addresses
Such as:
http://115.159.122.64:8000/alipay/return/
Important question: The current revisions are all local ones.
Our interpreter is an on-line address, if you modify it locally, do nothing. Without upload, the old version of the code on the server will be used for debugging.
- Note that the entire project directory folder should be selected for uploading.
Modify the order number.
- This time we have only modified Alipay.py and we can upload it separately.
Scan the code with your mobile phone and it jumps to the post method.
We observed the following parameters:
The data are stored in request.POST, and there are fields such as sign. We can check the signature with sign.
Make sure this is sent to us by Alipay.
Parameters for asynchronous notifications:
- Signature:
- Business parameter: trade_no is the transaction certificate number returned by Alipay.
- out_trade_no: Order number for our own platform
- Trade_status: Alipay's trading status
TRADE_FINISHED transaction completes false (no notification triggered)
TRADE_SUCCESS Payment Success true (Trigger Notification)
WAIT_BUYER_PAY transaction creates false (no notification triggered)
TRADE_CLOSED transaction closes false (no notification triggered)
We can define these in our model.
ORDER_STATUS = ( ("TRADE_SUCCESS", "Success"), ("TRADE_CLOSED", "Timeout closure"), ("WAIT_BUYER_PAY", "Transaction creation"), ("TRADE_FINISHED", "End of transaction"), ("paying", "To be paid"), )
The path of configuring Alipay related key is in setttings.py.
# Alipay related key path private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_2048.txt') ali_pub_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/alipay_key_2048.txt')
Programming post method
from utils.alipay import AliPay from VueDjangoFrameWorkShop.settings import ali_pub_key_path, private_key_path def post(self, request): """ //Handling Alipay's notify_url """ # 1. Remove sign first processed_dict = {} for key, value in request.POST.items(): processed_dict[key] = value sign = processed_dict.pop("sign", None) # 2. Generate an Alipay object alipay = AliPay( appid="", app_notify_url="http://115.159.122.64:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # Alipay's public key, which verifies the use of Alipay return message, is not your own public key. debug=True, # Default False, return_url="http://115.159.122.64:8000/alipay/return/" ) # 3. carry out a check to make sure that Alipay gave it to us. verify_re = alipay.verify(processed_dict, sign)
You can see our processed_dict
from rest_framework.response import Response # If the signature is checked successfully if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) # Query for orders that exist in the database existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: # Order item order_goods = existed_order.goods.all() # Increase in Sales of Goods Order Medium Value for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save() # Update the order status and fill in the transaction certificate number given by Alipay. existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() # success will return to Alipay, Alipay will not continue to send messages. return Response("success")
If the check fails, no information will be returned.
In fact, the logic of return is similar to that of notify, just requesting data from get.
If you want to be able to call back to the personal order page after payment, then the function of return url comes out.
def get(self, request): """ //Deal with Alipay's return_url return """ processed_dict = {} # 1. Getting parameters in GET for key, value in request.GET.items(): processed_dict[key] = value # 2. Remove sign sign = processed_dict.pop("sign", None) # 3. Generating ALipay objects alipay = AliPay( appid="2016091200490210", app_notify_url="http://115.159.122.64:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # Alipay's public key, which verifies the use of Alipay return message, is not your own public key. debug=True, # Default False, return_url="http://115.159.122.64:8000/alipay/return/" ) verify_re = alipay.verify(processed_dict, sign) # You can do nothing here. Because no matter whether the return url is sent or not. Notfy URL modifies the order status. if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save()
Alipay and vue tune in
Procedure: Log in again after exit, change localhost to online address
In the process of creating an order, the shopping cart is emptied, order_goods is created, and the order object is created.
Now we're going to cover payment, and there's the Payment Page page. How to generate the url of payment?We can rewrite createModelMixin's Create method. The url that Alipay pays is also put in.
Instead of overloading create, it's another function of Serializer
Serializer Method Field in Serializer fields
from django.contrib.auth.models import User from django.utils.timezone import now from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() class Meta: model = User def get_days_since_joined(self, obj): return (now() - obj.date_joined).days
Let's write our own functions and write logic in them. This makes it flexible and no longer dependent on a field in the data table.
In our OrderSerializer
When the user submits the order, we can add a field alipay_url to Serilizer to return it specially.
Alipay's payment link. It can't be submitted by users themselves. It's generated on the server side and returned to users.
A get before a field is a function that corresponds to it.
alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): alipay = AliPay( appid="2016091200490210", app_notify_url="http://115.159.122.64:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # Alipay's public key, which verifies the use of Alipay return message, is not your own public key. debug=True, # Default False, return_url="http://115.159.122.64:8000/alipay/return/" )
When you go online, remember to change debug to false, and when the parameters are changed, you can call the previous logic.
url = alipay.direct_pay( subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url
Because there will be more than one item in the order, the title will be changed to the order number of our own merchant.
return_url does not need to be retransmitted.
data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
Because it was already passed in when building_body in Alipay.
Don't forget to upload code from the server.
We also need to copy this logic into OrderDetail Serializer
alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): alipay = AliPay( appid="2016091200490210", app_notify_url="http://115.159.122.64:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # Alipay's public key, which verifies the use of Alipay return message, is not your own public key. debug=True, # Default False, return_url="http://115.159.122.64:8000/alipay/return/" ) url = alipay.direct_pay( subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url
Because the order is not paid for the immediate use of the order, Alipay payment also needs to jump to Alipay.
You can configure appid, return_url, etc. into setting s. Configurability will be higher
Look at what is done in vue.
In src/views/cart/cart.vue:
createOrder( { post_script:this.post_script, address:this.address, signer_name:this.signer_name, singer_mobile:this.signer_mobile, order_mount:this.totalPrice } ).then((response)=> { alert('Order Creation Success') window.location.href=response.data.alipay_url; }).
Successful order creation jumps to alipay_url in the interface's return data.
The message here is mandatory if you want to change it to optional. Modify the model.
We want to skip back to our order list page after payment is completed.
- Solution 1: Alipay does not return a page, but this picture, so that vue can display the picture. Make your own jump inside the page.
Its return_url is actually a URL of the server. The server could not notify our vue to update.
- Solution 2
Simple violence. Let django return to this page. No node.js is allowed to proxy this page.
It's like a template mechanism. So there is no cross-domain problem.
Incorporate vue into django project
It's about deployment.
vue has two development modes: dev mode and build mode.
npm run build
build is used to generate html and static files.
First, copy index.html into the template directory.
- Then create a new static directory
Set MEDIA_URL in our setting file
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ]
Modify references in index.html
# home page path('index/', TemplateView.as_view(template_name='index.html'))
Static file problem 404:
STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') re_path('static/(?P<path>.*)', serve, {"document_root": STATIC_ROOT}),
At this point, we can access our home page through http://remote ip address: 8000/index/.
Add name to the home path
path('index/', TemplateView.as_view(template_name='index.html'),name='index'),
When return ing in trade/views.py:
from django.shortcuts import render, redirect response = redirect("index") # Hope to jump to the vue project directly to help us jump to the payment page response.set_cookie("nextPath","pay", max_age=3) return response else: response = redirect("index") return response
In fact, I wrote a logic in vue.
src/router/index.js
//Routing judgment router.beforeEach((to, from, next) => { var nextPath = cookie.getCookie('nextPath') console.log(nextPath) if(nextPath=="pay"){ next({ path: '/app/home/member/order', }); }
If the cookie has a nextpath, jump directly to the order. max_age is set as short as possible so that it can expire once taken.
Change success in order.vue to TRADE_SUCCESS consistent with our model
src/views/member/orderDetail.vue
Do the same thing
Another pattern is that Alipay gives us a picture. Let vue be embedded in the picture.
Parameter: qr_pay_model is set to 4. Order code generates a picture
Chapter 10 Concluding
The teacher buried several pits.
The Alipay interface will initiate the post request first, and then respond to the get request. The return parameter of the return URL does not contain our payment status, so the default value none, which was not obtained by the get request, will overwrite the payment status originally written by the post request.
- When debugging with vue, errors will be reported
Uncaught RangeError: Maximum call stack size exceeded
The front-end is not familiar, so I change the teacher's setcookie, the front-end value, if there is this value, the jump order page to the back-end implementation.
response = redirect("/index/#/app/home/member/order") # response.set_cookie("nextPath","pay", max_age=3)
This is the result of my own exploration of debug. If it helps you, please reward it.