19- vue django restful framework to create fresh supermarket - Alipay payment source interpretation

Keywords: Vue Python Django Front-end

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

mark

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&notify_url=http://127.0.0.1:8000/alipay/return/&return_url=http://127.0.0.1:8000/alipay/return/&sign_type=RSA2&timestamp=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)
mark

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&timestamp=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:

mark

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)
mark

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.

mark

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.

mark

First, copy index.html into the template directory.

mark
  • Then create a new static directory
mark

Set MEDIA_URL in our setting file

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

Modify references in index.html

mark
    # 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/.

mark

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

mark

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.

Posted by lhcpr on Fri, 21 Dec 2018 00:33:05 -0800