day88:luffy: Alipay synchronous result notification & receive asynchronous payment result & user purchase record & my order

content

1. Alipay synchronization result notification

2. User purchase record table

3. Accept asynchronous payment results

4. Aftermath

5. My order

1. Alipay synchronization result notification

1.get request Alipay, Alipay returns the parameters to you

When the user enters the username and password to confirm the payment, Alipay will reply a url to the vue front end. This url is very long and contains many parameters. The parameters are as follows:

http://www.luffycity.cn:8080/payments/result?
charset=utf8&
out_trade_no=20190929151453000001000020&
method=alipay.trade.page.pay.return&
total_amount=310.00&
sign=kebIZBI%2FpCNXmCivfJPPw21gcobulPZoSh%2BXiHR8l6cgexQi2STG4AZgr%2FEUhvc5kEMacJLvCmBaw1Wqo4WK3sPzbUaPmzq3NshUNzYK2lWTsmOauidNxlk1bK0Q%2FANBfQUkmj6TQjyB5T9QqEnS80KFsDrGrasU%2B%2Fz9W%2FjOCLrSji6TnKhRkI9pqBMdw823ABU75b7zOtXzcXKduO%2B6vsXVvluMzedss9dHs1celxPAWQV9jcKjzq%2F1bPbZcmgAGNQQecoJ%2BFSc3uTmTk24uV39PM54LIlg8aeRlkPNjvhBkJh%2FG0%2BURNDdG2593IFIF%2BUqoU%2F7ixm19dX222GCWg%3D%3D&
trade_no=2019092922001439881000120282&
auth_app_id=2016091600523592&
version=1.0&
app_id=2016091600523592&
sign_type=RSA2&
seller_id=2088102175868026&
timestamp=2019-09-29%2015%3A15%3A53

The meaning of these parameters: https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay

2. The back-end implements the view of processing the result of Alipay synchronization notification

Alipay passes the parameters to the vue front-end, and the vue front-end wants these parameters to be sent to the back-end, so that the back-end can verify these parameters. Determine whether this url is sent by Alipay

Back-end verification of those parameters returned to you by Alipay

payment/urls.py

from django.urls import path,re_path
from . import views

urlpatterns = [
    path('result/',views.AlipayResultView.as_view(),)
]

payment/view.py

class AlipayResultView(APIView):
    permission_classes = [IsAuthenticated, ]
    def get(self,request):
        
        # 1.create alipay object
        alipay = AliPay(
            appid=settings.ALIAPY_CONFIG['appid'],
            app_notify_url=None,  # default callback url
            app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(),
            # Alipay's public key, used to verify Alipay's return message, not your own public key,
            alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(),
            sign_type=settings.ALIAPY_CONFIG['sign_type'],  # RSA or RSA2
            debug=settings.ALIAPY_CONFIG['debug'],  # default False
        )
        # 2.Verify Alipay response data
        data = request.query_params.dict() # Get that bunch of parameters
        
        out_trade_no = data.get('out_trade_no') # Get merchant order number
        sign = data.pop('sign') # get signature
        success = alipay.verify(data,sign) # Use the parameters and signature to verify whether the bunch of parameters are sent by Alipay
        if not success:
            logger.error('%s,Alipay response data verification failed' % out_trade_no)
            return Response('Alipay response data verification failed',status=status.HTTP_400_BAD_REQUEST)

        #  response result
        return Response({'msg':'ok','data':res_data})



 

3. The vue front end sends a request to verify the get parameters

The front-end sends a request to pass a large number of parameters sent by Alipay to the vue front-end to the back-end, and the back-end returns a successful result after the verification is completed.

If there is no problem with the verification, it can show that the purchase is successful.

Success.vue

created(){
      // Forward the payment result on the address bar to the backend
      this.send_alipay_params();

methods:{
      send_alipay_params(){
        let token = localStorage.token || sessionStorage.token;
        this.$axios.get(`${this.$settings.Host}/payment/result/${location.search}`,{
          headers:{
              'Authorization':'jwt ' + token
            }
        }).then((res)=>{ // If there is no problem with the back-end verifying these parameters, the parameters required by the purchase success page can be passed over
          this.pay_time = res.data.data.pay_time; // Payment time
          this.course_list = res.data.data.course_list; // Purchase Course List
          this.total_real_price = res.data.data.total_real_price; // The total real price of the course
        }).catch((error)=>{
          this.$message.error(error.response.data.msg);
        })
      },

2. User purchase record table

When the user purchases successfully, a user record should be generated, which is mainly used to store the serial number of the payment platform. With this serial number, you can go to Alipay to check the bill. and when the course was purchased and expired.

users/models.py

class UserCourse(BaseModel):
    """User's course purchase history"""
    pay_choices = (
        (1, 'User purchases'),
        (2, 'free event'),
        (3, 'event giveaway'),
        (4, 'system gift'),
    )


    user = models.ForeignKey(User, related_name='user_courses', on_delete=models.DO_NOTHING, verbose_name="user")
    course = models.ForeignKey(Course, related_name='course_users', on_delete=models.DO_NOTHING, verbose_name="course")
    trade_no = models.CharField(max_length=128, null=True, blank=True, verbose_name="The serial number of the payment platform", help_text="In the future, rely on the serial number to check the bill on the payment platform")
    buy_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="Ways to purchase")
    pay_time = models.DateTimeField(null=True, blank=True, verbose_name="purchase time")
    out_time = models.DateTimeField(null=True, blank=True, verbose_name="Expiration") # null means never expires

    class Meta:
        db_table = 'ly_user_course'
        verbose_name = 'Course Purchase History'
        verbose_name_plural = verbose_name

3. Accept asynchronous payment results

payment/views.py

class AlipayResultView(APIView):
    permission_classes = [IsAuthenticated, ]
    def post(self,request):
        
        # create alipay object
        alipay = AliPay(
            appid=settings.ALIAPY_CONFIG['appid'],
            app_notify_url=None,  # default callback url
            app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(),
            # Alipay's public key, used to verify Alipay's return message, not your own public key,
            alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(),
            sign_type=settings.ALIAPY_CONFIG['sign_type'],  # RSA or RSA2
            debug=settings.ALIAPY_CONFIG['debug'],  # default False
        )
        
        # Verify Alipay response data
        data = request.data.dict()
        sign = data.pop('sign')
        success = alipay.verify(data,sign)
        if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):

            self.change_order_status(data)

            return Response('success')

4. Aftermath

When everything is done, there are a few more things to do:

1. Modify order status

2. Deduct coupon points

3. Clear the shopping cart data and all data on the checkout page

4. Store the relevant information in the user purchase record table

payment/views.py

class AlipayResultView(APIView):    
    def change_order_status(self,data): # Edit order status
            with transaction.atomic():
                out_trade_no = data.get('out_trade_no')
                trade_no = data.get('trade_no')

                # A.Edit order status  
                # 1.Get the order object of the current order number
                order_obj = Order.objects.get(order_number=out_trade_no)

                # 2.Change the order status of the current order to 1: Paid
                order_obj.order_status = 1

                # 3.Save order information
                order_obj.save()

                # B.Modify the status of the coupon
                if order_obj.coupon > 0: # If the user uses the coupon
                    # 1.Get the coupon object used by the user
                    user_coupon_obj = UserCoupon.objects.get(is_use=False, id=order_obj.coupon)

                    # 2.Change the status of the coupon used by the user from unused to used
                    user_coupon_obj.is_use = True

                    # 3.Save the user's coupon information
                    user_coupon_obj.save()

                # C.payment successful,User points should be deducted accordingly

                # 1.Check how many points are used for the current order
                use_credit = order_obj.credit

                # 2.Query the user's total points minus the used points to get the user's remaining points
                self.request.user.credit -= use_credit

                # 3.Save the user's points information
                self.request.user.save()

                # D.Save the transaction serial number of Alipay(purchase record)

                # 1.Reverse query to all order detail objects through the order object
                order_detail_objs = order_obj.order_courses.all()

                # 2.get current time
                now = datetime.datetime.now()

               # Purchased successfully from redis Delete the selected state of the course
                conn = get_redis_connection('cart')
                pipe = conn.pipeline()
                pipe.delete('selected_cart_%s' % self.request.user.id)

                # Need to give purchase success page(Success.vue data returned)
                res_data = {
                    'pay_time': now,
                    'course_list': [],
                    'total_real_price': order_obj.real_price,
                }

                for order_detail in order_detail_objs:
                    # Number of students who purchased successful courses+1
                    course = order_detail.course
                    course.students += 1
                    course.save()

                    # A successful course purchase should show the course list for each course
                    res_data['course_list'].append(course.name)

                   # Get the validity period value of the current course from the course details page
                    expire_id = order_detail.expire
                    if expire_id > 0: # if not permanent

                        # according to expire_id Check the validity period of the course model object
                        expire_obj = CourseExpire.objects.get(id=expire_id)

                        # Check the validity period of the course(days)
                        expire_time = expire_obj.expire_time

                        # Calculate the expiration time of a course
                        out_time = now + datetime.timedelta(days=expire_time)
                    else: # If it is permanent, there is no expiration time
                        out_time = None

                    # After everything is processed, the relevant information is stored in the user purchase record table
                    UserCourse.objects.create(**{
                        'user':self.request.user,
                        'course':course,
                        'trade_no':trade_no,
                        'buy_type':1,
                        'pay_time':now,
                        'out_time':out_time,

                    })
                    # shopping cart redis data deletion
                    pipe.hdel('cart_%s' % self.request.user.id, course.id)
                pipe.execute()

            return res_data

order/serializers.py

def create:
    order_obj.coupon = coupon_id
    order_obj.credit = credit
    order_obj.save()

5. My order

1. My order interface - initialization

Myorder.vue

<template>
  <div class="user-order">
    <Vheader/>
    <div class="main">
        <div class="banner"></div>
          <div class="profile">
              <div class="profile-info">
                  <div class="avatar"><img class="newImg" width="100%" alt="" src="../../static/img/logo@2x.png"></div>
                  <span class="user-name">Wu Moumou</span>
                  <span class="user-job">Beijing | programmer</span>
              </div>
              <ul class="my-item">
                  <li>my account</li>
                  <li class="active">My Order</li>
                  <li>personal information</li>
                  <li>Account security</li>
              </ul>
            </div>
            <div class="user-data">
              <ul class="nav">
                <li class="order-info">Order</li>
                <li class="course-expire">Validity period</li>
                <li class="course-price">Course Price</li>
                <li class="real-price">The amount actually paid</li>
                <li class="order-status">trading status</li>
                <li class="order-do">transaction operation</li>
              </ul>
              <div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index">
                  <div class="user-data-header">
                    <span class="order-time">xxxx</span>
                    <span class="order-num">order number:
                        <span class="my-older-number">xxxx</span>
                    </span>
                  </div>
                  <ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data">
                  <li class="order-info">
                      <img :src="course_obj.course_img" alt="">
                      <div class="order-info-title">
                        <p class="course-title">xxxx</p>
                        <p class="price-service">xxxx</p>
                      </div>
                  </li>
                  <li class="course-expire">xxxx</li>
                  <li class="course-price">xxxx</li>
                  <li class="real-price">xxxx</li>
                  <li class="order-status">xxxx</li>
                  <li class="order-do">
                    <span class="btn btn2" v-if="order_obj.get_order_status_display==='Paid'">to learn</span>
                    <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='unpaid'" @click="go_pay(order_obj.order_number)">to pay</span>
                    <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='timeout cancel'">timeout cancel</span>
                    <span class="btn btn2" v-else>Cancelled</span>
                  </li>
                </ul>
              </div>
          </div>
    </div>
    <Footer/>
  </div>
</template>

<script>
  import Vheader from "./common/Vheader"
  import Footer from "./common/Footer"
  export default{
    name:"Myorder",
    data(){
      return {
       

      };
    },
    created(){
  

    },
    methods:{

     
    },
    components:{
      Vheader,
      Footer,
    }
  }
</script>

<style scoped>
.main .banner{
    width: 100%;
    height: 324px;
    background: url(../../static/img/my_bkging.0648ebe.png) no-repeat;
    background-size: cover;
    z-index: 1;
}
.profile{
    width: 1200px;
    margin: 0 auto;
}
.profile-info{
    text-align: center;
    margin-top: -80px;
}
.avatar{
    width: 120px;
    height: 120px;
    border-radius: 60px;
    overflow: hidden;
    margin: 0 auto;
}
.user-name{
    display: block;
    font-size: 24px;
    color: #4a4a4a;
    margin-top: 14px;
}
.user-job{
    display: block;
    font-size: 11px;
    color: #9b9b9b;
 }
.my-item{
    list-style: none;
    line-height: 1.42857143;
    color: #333;
    width: 474px;
    height: 31px;
    display: -ms-flexbox;
    display: flex;
    cursor: pointer;
    margin: 41px auto 0;
    -ms-flex-pack: justify;
    justify-content: space-between;
}
.my-item .active{
    border-bottom: 1px solid #000;
}
.user-data{
    width: 1200px;
    height: auto;
    margin: 0 auto;
    padding-top: 30px;
    border-top: 1px solid #e8e8e8;
    margin-bottom: 63px;
}
.nav{
    width: 100%;
    height: 60px;
    background: #e9e9e9;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: center;
    align-items: center;
}
.nav li{
    margin-left: 20px;
    margin-right: 28px;
    height: 60px;
    line-height: 60px;
    list-style: none;
    font-size: 13px;
    color: #333;
    border-bottom: 1px solid #e9e9e9;
  width: 160px;
}
.nav .order-info{ width: 325px; }
.nav .course-expire{ width: 60px; }
.nav .course-price{ width: 130px; }
.user-data-header{
    display: flex;
    height: 44px;
    color: #4a4a4a;
    font-size: 14px;
    background: #f3f3f3;
    -ms-flex-align: center;
    align-items: center;
}
.order-time{
    font-size: 12px;
    display: inline-block;
    margin-left: 20px;
}
.order-num{
    font-size: 12px;
    display: inline-block;
    margin-left: 29px;
}
.user-data-list{
    height: 100%;
    display: flex;
}
.user-data-list{
  background: none;
}
.user-data-list li{
    height: 60px;
    line-height: 60px;
}
.user-data-list .order-info{
    display: flex;
    align-items: center;
    margin-right: 28px;
}
.user-data-list .order-info img{
    max-width: 100px;
    max-height: 75px;
    margin-right: 22px;
}
.course-title{
    width: 203px;
    font-size: 13px;
    color: #333;
    line-height: 5px;
    margin-top: -10px;
}
.order-info-title .price-service{
    line-height: 18px;
}
.price-service{
    font-size: 12px;
    color: #fa6240;
    padding: 0 5px;
    border: 1px solid #fa6240;
    border-radius: 4px;
    margin-top: 4px;
    position: absolute;
}
.order-info-title{
    margin-top: -10px;
}
.user-data-list .course-expire{
    font-size: 12px;
    color: #ff5502;
    width: 60px;
    text-align: center;
}
.btn {
  width: 100px;
  height: 32px;
  font-size: 14px;
  color: #fff;
  background: #ffc210;
  border-radius: 4px;
  border: none;
  outline: none;
  transition: all .25s ease;
  display: inline-block;
  line-height: 32px;
  text-align: center;
  cursor: pointer;
}
</style>

index.js

{
      path: '/myorder/',   
      component: Myorder
    },

2. My order page - backend interface

users/urls.py

urlpatterns = [
    path(r'myorder/', views.MyOrderView.as_view()),
]

users/views.py

class MyOrderView(ListAPIView):
    permission_classes = [IsAuthenticated, ]
    serializer_class = MyOrderModelSerializer

    def get_queryset(self):
        return Order.objects.filter(user=self.request.user)

user/serializers.py

class MyOrderModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = ['id', 'order_number' ,'pay_time', 'get_order_status_display', 'order_detail_data']

order/models.py

In my order page, I need to display some data

class Order(BaseModel):
    def order_detail_data(self):
        # Get all course details objects
        order_detail_objs = self.order_courses.all()
        data_list = []

        for order_detail in order_detail_objs:
            expire_id = order_detail.expire
            
            # Determined by expiration date expire_text what to return
            if expire_id > 0:
                expire_obj = CourseExpire.objects.get(id=expire_id)
                expire_text = expire_obj.expire_text
            else:
                expire_text = 'Permanent'
        
            # Fields that each course should contain
            order_dict = {
                'course_img':contains.SERVER_ADDR + order_detail.course.course_img.url,
                'course_name':order_detail.course.name,
                'expire_text':expire_text,
                'price':order_detail.price,
                'real_price': self.real_price,
                'discount_name':order_detail.discount_name,

            }
            # Add the details of each course to a list and return it to the front end
            data_list.append(order_dict)

        return data_list
    

3. My order page - front end

1. Get back-end order data

Myorder.vue

// js
get_order_data(){
        // Check if the current visitor is logged in!
        let token = localStorage.token || sessionStorage.token;
        this.$axios.get(`${this.$settings.Host}/users/myorder/`,{
          headers:{
              'Authorization':'jwt ' + token
            }
        }).then((res)=>{
          this.order_list = res.data;
        }).catch((error)=>{

        })
<!-- html -->
<div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index">
    <div class="user-data-header">
        <span class="order-time">{{order_obj.pay_time.replace('T', ' ')}}</span>
        <span class="order-num">order number:
            <span class="my-older-number">{{order_obj.order_number}}</span>
        </span>
    </div>
    <ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data">
        <li class="order-info">
            <img :src="course_obj.course_img" alt="">
            <div class="order-info-title">
                <p class="course-title">{{course_obj.course_name}}</p>
                <p class="price-service">{{course_obj.discount_name}}</p>
            </div>
        </li>
        <li class="course-expire">{{course_obj.expire_text}}</li>
        <li class="course-price">{{course_obj.price}}</li>
        <li class="real-price">{{course_obj.real_price}}</li>
        <li class="order-status">{{order_obj.get_order_status_display}}</li>
        <li class="order-do">
            <span class="btn btn2" v-if="order_obj.get_order_status_display==='Paid'">to learn</span>
            <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='unpaid'" @click="go_pay(order_obj.order_number)">to pay</span>
            <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='timeout cancel'">timeout cancel</span>
            <span class="btn btn2" v-else>Cancelled</span>
        </li>
    </ul>
</div>
         

2. Click to pay on my order page

Myorder.vue

<!-- html -->
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='unpaid'" @click="go_pay(order_obj.order_number)">to pay</span>                   
// js
go_pay(order_number){
        let token = localStorage.token || sessionStorage.token;
        this.$axios.get(`${this.$settings.Host}/payment/alipay/?order_number=${order_number}`,{
              headers:{
              'Authorization':'jwt ' + token
            }

        }).then((res)=>{
          location.href = res.data.url;

        }).catch((error)=>{
          this.$message.error(error.response.data.msg);
        })

Posted by benjaminbeazy on Sun, 08 May 2022 06:25:29 +0300