Using transaction and accessing Alipay payment function in django

Keywords: Python Django OpenSSL Database SDK

I have always wanted to record the transactions used in the project and the payment function of Alipay. I have been lazy all the time. I will take a look at this while I am a bit interested today.

The essential function of the mall project is to pay the order, so it will involve the preservation of the order and the introduction of the payment interface. At the beginning of the database model, the order is divided into two tables, one is the order form, recording the basic information of the order, such as order number, user information, freight and so on, and the other is the order commodity form, recording the commodity information in the order. When saving an order, it will certainly involve the creation and preservation of two tables. In fact, there is also a table that needs some modifications. That is the commodity table. When an order is successfully saved, it means that the transaction is successful. When the commodity is sold, the inventory of the commodity should be modified. So there are three tables involved in the operation of saving orders. Therefore, when saving an order, the modification of multi-table data should succeed or fail at the same time, which is very similar to the transactions in the database. Therefore, transaction is introduced here to complete the function of order saving.

In Django, a transaction can be defined by atomic provided by the django.db.transaction module. atomic provides two usages, one is decorator, the other is with statement.

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code is executed in a transaction
    ...
from django.db import transaction

def viewfunc(request):
    # This part of the code is not in the transaction, will be Django Automatic submission
    ...

    with transaction.atomic():
        # This part of the code is executed in the transaction
        ...    

In Django, savepoint support is also provided, savepoint can be created in transactions to record the specific state of data, database error can be restored to the state of data savepoint.

from django.db import transaction

# Create savepoints
save_id = transaction.savepoint()  

# Roll back to savepoint
transaction.savepoint_rollback(save_id)

# Submit all database transaction operations from savepoint to current state
transaction.savepoint_commit(save_id)

So, in the Create method of serializer, you can create a transaction, modify and save the data, and create new ones. If something goes wrong, you can roll back directly and commit the transaction if there is no problem. The code is as follows

    def create(self, validated_data):
        """
        //Save the order
        """
        # Get the current single user
        user = self.context['request'].user

        # Organizational Order No. 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']

        # Generating orders
        with transaction.atomic():
            # Create a savepoint
            save_id = transaction.savepoint()

            try:
                # Create order information
                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']
                )
                # Get shopping cart information
                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)

                # Convert bytes type to int type
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])

                # # Query out all commodity data at once
                # skus = SKU.objects.filter(id__in=cart.keys())

                # Processing Order Goods
                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]

                        # Judging Inventory
                        origin_stock = sku.stock  # Original stock
                        origin_sales = sku.sales  # Original sales

                        if sku_count > origin_stock:
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('Insufficient stock of goods')

                        # Used for demonstrating concurrent orders
                        # import time
                        # time.sleep(5)

                        # Reduce stock
                        # sku.stock -= sku_count
                        # sku.sales += sku_count
                        # sku.save()
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # Update according to the original inventory condition, return the number of items updated, optimistic lock
                        ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                        if ret == 0:
                            continue

                        # Accumulated Sales Information of SPU s for Commodities
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # Data for accumulating basic order information
                        order.total_count += sku_count  # Cumulative total amount
                        order.total_amount += (sku.price * sku_count)  # Cumulative total

                        # Keep the order merchandise
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=sku_count,
                            price=sku.price,
                        )

                        # Update Successful
                        break

                # Update order amount information
                order.total_amount += order.freight
                order.save()

            except serializers.ValidationError:
                raise
            except Exception as e:
                logger.error(e)
                transaction.savepoint_rollback(save_id)
                raise

            # Submission transaction
            transaction.savepoint_commit(save_id)

            # Update the shopping cart data saved in redis
            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

Another point to note is that when an order is submitted, the corresponding goods in the shopping cart should be deleted. All right, that's the transaction in django.

Let's talk about the introduction of Alipay's payment function. Now basically all projects involve third party payment when it comes to payment function. The most widely used ones should be Alipay and WeChat. I use Alipay payment here. When the order is created, the next step is to pay.

Alipay development platform web site https://open.alipay.com/platform/home.htm . Because the developer account needs to be audited, Alipay has provided sandbox environment for testing. Sandbox applications: https://docs.open.alipay.com/200/105311 . Sandbox account: https://openhome.alipay.com/platform/appDaily.htm?tab=account . python docking Alipay SDK: https://github.com/fzlee/alipay/blob/master/README.zh-hans.md. SDK: https://docs.open.alipay.com/270/106291/.

First, flow the process. The user clicks the button to request the Alipay payment interface. First, log in. After the login is successful, the payment operation will be carried out. The successful payment will be callback.

First, the first step is to click on the button. The url will be spliced back to the front end. The url will be returned to the front end, the front end will jump, jump to the Alipay related interface, and the user will login and pay.

Looking at pythonsdk, we can first generate a key (public key and private key) by using the openssl command. The private key is kept by itself and checked by the public key user. The order is as follows:

openssl
OpenSSL > genrsa-out app_private_key. PEM 2048 # private key
OpenSSL > RSA - in app_private_key. PEM - pubout - out app_public_key. PEM  export public key
OpenSSL> exit

At the same time, you need to get a public key string from Alipay. https://github.com/fzlee/alipay/blob/master/tests/certs/ali/ali_public_key.pem
Since we use the RSA generated key above, we also need RSA's public key in Alipay.

 


With the key set, we can start to write the view. The code is as follows:

class PaymentView(APIView):
    """
    //payment
    """
    permission_classes = (IsAuthenticated,)

    def get(self, request, order_id):
        """
        //Get Payment Links
        """
        # Determine whether the order information is correct
        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': 'Error in order information'}, status=status.HTTP_400_BAD_REQUEST)

        # Constructing Alipay payment link address
        alipay = AliPay(
            appid=settings.ALIPAY_APPID,
            app_notify_url=None,  # Default callback 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"),  # Alipay's public key, which verifies the use of Alipay return message, is not your own public key.,
            sign_type="RSA2",  # RSA perhaps RSA2
            debug=settings.ALIPAY_DEBUG  # default False
        )

        order_string = alipay.api_alipay_trade_page_pay(
            out_trade_no=order_id,
            total_amount=str(order.total_amount),
            subject="Medo Mall%s" % order_id,
            return_url="http://www.meiduo.site:8080/pay_success.html", 
        )
        # Need to jump to https://openapi.alipay.com/gateway.do? + order_string
        # Stitching links back to the front end
        alipay_url = settings.ALIPAY_URL + "?" + order_string
        return Response({'alipay_url': alipay_url})

 

Relevant parameters can be configured in the configuration file (ALPAY_APPID, ALPAY_URL, ALPAY_DEBUG) beforehand to enable the sandbox environment when ALPAY is True. When the payment is successful, the callback address you filled in will be called back. The parameters returned are as follows.

 

 

 

The front-end page sends this data to the back-end, which checks and saves the payment results. This is the whole process. The specific process can be referred to pythonsdk.

I QQ: 595395786, welcome to exchange!!

 

 




Posted by trauch on Fri, 06 Sep 2019 21:05:09 -0700