https://docs.open.alipay.com/270/105900/

在github上搜索alipay

https://docs.open.alipay.com/291/105972/

下载之后安装启动

  1. """
  2. 1)支付宝API:六大接口
  3. https://docs.open.alipay.com/270/105900/
  4. 2)支付宝工作流程(见下图):
  5. https://docs.open.alipay.com/270/105898/
  6. 3)支付宝8次异步通知机制(支付宝对我们服务器发送POST请求,索要 success 7个字符)
  7. https://docs.open.alipay.com/270/105902/
  8. """
  1. # 1、在沙箱环境下实名认证:https://openhome.alipay.com/platform/appDaily.htm?tab=info
  2. # 2、电脑网站支付API:https://docs.open.alipay.com/270/105900/
  3. # 3、完成RSA密钥生成:https://docs.open.alipay.com/291/105971
  4. # 4、在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容
  5. # 5、Python支付宝开源框架:https://github.com/fzlee/alipay
  6. # >: pip install python-alipay-sdk --upgrade
  7. # 7、公钥私钥设置
  8. """
  9. # alipay_public_key.pem
  10. -----BEGIN PUBLIC KEY-----
  11. 支付宝公钥
  12. -----END PUBLIC KEY-----
  13. # app_private_key.pem
  14. -----BEGIN RSA PRIVATE KEY-----
  15. 用户私钥
  16. -----END RSA PRIVATE KEY-----
  17. """
  18. # 8、支付宝链接
  19. """
  20. 开发:https://openapi.alipay.com/gateway.do
  21. 沙箱:https://openapi.alipaydev.com/gateway.do
  22. """

  1. https://github.com/fzlee/alipay
  1. >: pip install python-alipay-sdk --upgrade
  2. # 如果抛ssl相关错误,代表缺失该包
  3. >: pip install pyopenssl
  1. libs
  2. ├── iPay # aliapy二次封装包
  3. ├── __init__.py # 包文件
  4. ├── pem # 公钥私钥文件夹
  5. ├── alipay_public_key.pem # 支付宝公钥文件
  6. ├── app_private_key.pem # 应用私钥文件
  7. ├── pay.py # 支付文件
  8. └── └── settings.py # 应用配置
  1. -----BEGIN PUBLIC KEY-----
  2. 拿应用公钥跟支付宝换来的支付宝公钥
  3. -----END PUBLIC KEY-----
  1. -----BEGIN RSA PRIVATE KEY-----
  2. 通过支付宝公钥私钥签发软件签发的应用私钥
  3. -----END RSA PRIVATE KEY-----
  1. import os
  2. # 应用私钥
  3. APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), \'pem\', \'app_private_key.pem\')).read()
  4. # 支付宝公钥
  5. ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), \'pem\', \'alipay_public_key.pem\')).read()
  6. # 应用ID
  7. APP_ID = \'2016093000631831\'
  8. # 加密方式
  9. SIGN = \'RSA2\'
  10. # 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
  11. DEBUG = True
  12. # 支付网关
  13. GATEWAY = \'https://openapi.alipaydev.com/gateway.do\' if DEBUG else \'https://openapi.alipay.com/gateway.do\'
  1. from alipay import AliPay
  2. from . import settings
  3. # 支付对象
  4. alipay = AliPay(
  5. appid=settings.APP_ID,
  6. app_notify_url=None,
  7. app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
  8. alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
  9. sign_type=settings.SIGN,
  10. debug=settings.DEBUG
  11. )
  12. # 支付网关
  13. gateway = settings.GATEWAY
  1. # 包对外提供的变量
  2. from .pay import gateway, alipay
  1. # 上线后必须换成公网地址
  2. # 后台基URL
  3. BASE_URL = \'http://127.0.0.1:8000\'
  4. # 前台基URL
  5. LUFFY_URL = \'http://127.0.0.1:8080\'
  6. # 支付宝同步异步回调接口配置
  7. # 后台异步回调接口
  8. NOTIFY_URL = BASE_URL + "/order/success/"
  9. # 前台同步回调接口,没有 / 结尾
  10. RETURN_URL = LUFFY_URL + "/pay/success"
  1. from django.urls import path, include
  2. from utils.router import router
  3. from . import views
  4. """
  5. 1)支付接口(需要登录认证:是谁):前台提交商品等信息,得到支付链接
  6. post方法
  7. 分析:支付宝回调
  8. 同步:get给前台 => 前台可以在收到支付宝同步get回调时,ajax异步在给消息同步给后台,也采用get,后台处理前台的get请求
  9. 异步:post给后台 => 后台直接处理支付宝的post请求
  10. 2)支付回调接口(不需要登录认证:哪个订单(订单信息中有非对称加密)、支付宝压根不可能有你的token):
  11. get方法:处理前台来的同步回调(不一定能收得到,所有不能在该方法完成后台订单状态等信息操作)
  12. post方法:处理支付宝来的异步回调
  13. 3)订单状态确认接口:随你前台任何时候来校验订单状态的接口
  14. """
  15. # 支付接口(生成订单)
  16. router.register(\'pay\', views.PayViewSet, \'pay\')
  17. urlpatterns = [
  18. path(\'\', include(router.urls)),
  19. path(\'success/\', views.SuccessViewSet.as_view({\'get\': \'get\', \'post\': \'post\'}))
  20. ]
  1. """
  2. class Order(models.Model):
  3. # 主键、总金额、订单名、订单号、订单状态、创建时间、支付时间、流水号、支付方式、支付人(外键) - 优惠劵(外键,可为空)
  4. pass
  5. class OrderDetail(models.Model):
  6. # 订单号(外键)、商品(外键)、实价、成交价 - 商品数量
  7. pass
  8. """
  1. from django.db import models
  2. from user.models import User
  3. from course.models import Course
  4. class Order(models.Model):
  5. """订单模型"""
  6. status_choices = (
  7. (0, \'未支付\'),
  8. (1, \'已支付\'),
  9. (2, \'已取消\'),
  10. (3, \'超时取消\'),
  11. )
  12. pay_choices = (
  13. (1, \'支付宝\'),
  14. (2, \'微信支付\'),
  15. )
  16. subject = models.CharField(max_length=150, verbose_name="订单标题")
  17. total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
  18. out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
  19. trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号")
  20. order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
  21. pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
  22. pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
  23. user = models.ForeignKey(User, related_name=\'order_user\', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
  24. created_time = models.DateTimeField(auto_now_add=True, verbose_name=\'创建时间\')
  25. class Meta:
  26. db_table = "luffy_order"
  27. verbose_name = "订单记录"
  28. verbose_name_plural = "订单记录"
  29. def __str__(self):
  30. return "%s - ¥%s" % (self.subject, self.total_amount)
  31. @property
  32. def courses(self):
  33. data_list = []
  34. for item in self.order_courses.all():
  35. data_list.append({
  36. "id": item.id,
  37. "course_name": item.course.name,
  38. "real_price": item.real_price,
  39. })
  40. return data_list
  41. class OrderDetail(models.Model):
  42. """订单详情"""
  43. order = models.ForeignKey(Order, related_name=\'order_courses\', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单")
  44. course = models.ForeignKey(Course, related_name=\'course_orders\', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程")
  45. price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
  46. real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
  47. class Meta:
  48. db_table = "luffy_order_detail"
  49. verbose_name = "订单详情"
  50. verbose_name_plural = "订单详情"
  51. def __str__(self):
  52. try:
  53. return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
  54. except:
  55. return super().__str__()
  1. from rest_framework.viewsets import GenericViewSet, ViewSet
  2. from rest_framework.mixins import CreateModelMixin
  3. from rest_framework.permissions import IsAuthenticated
  4. from rest_framework.response import Response
  5. from . import models, serializers
  6. # 支付接口
  7. class PayViewSet(GenericViewSet, CreateModelMixin):
  8. permission_classes = [IsAuthenticated]
  9. queryset = models.Order.objects.all()
  10. serializer_class = serializers.PaySerializer
  11. # 重写create方法,返回pay_url,pay_url是在serializer对象中,所以要知道serializer
  12. def create(self, request, *args, **kwargs):
  13. serializer = self.get_serializer(data=request.data, context={\'request\': request})
  14. serializer.is_valid(raise_exception=True)
  15. self.perform_create(serializer)
  16. return Response(serializer.context[\'pay_url\'])
  1. from rest_framework import serializers
  2. from . import models
  3. from course.models import Course
  4. from rest_framework.exceptions import ValidationError
  5. from django.conf import settings
  6. class PaySerializer(serializers.ModelSerializer):
  7. # 要支持单购物和群购物(购物车),前台要提交 课程主键(们)
  8. courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
  9. class Meta:
  10. model = models.Order
  11. fields = (\'subject\', \'total_amount\', \'pay_type\', \'courses\')
  12. extra_kwargs = {
  13. \'total_amount\': {
  14. \'required\': True
  15. },
  16. \'pay_type\': {
  17. \'required\': True
  18. },
  19. }
  20. # 订单总结校验
  21. def _check_total_amount(self, attrs):
  22. courses = attrs.get(\'courses\')
  23. total_amount = attrs.get(\'total_amount\')
  24. total_price = 0
  25. for course in courses:
  26. total_price += course.price
  27. if total_price != total_amount:
  28. raise ValidationError(\'total_amount error\')
  29. return total_amount
  30. # 生成订单号
  31. def _get_out_trade_no(self):
  32. import uuid
  33. code = \'%s\' % uuid.uuid4()
  34. return code.replace(\'-\', \'\')
  35. # 获取支付人
  36. def _get_user(self):
  37. return self.context.get(\'request\').user
  38. # 获取支付链接
  39. def _get_pay_url(self, out_trade_no, total_amount, subject):
  40. from libs import iPay
  41. order_string = iPay.alipay.api_alipay_trade_page_pay(
  42. out_trade_no=out_trade_no,
  43. total_amount=float(total_amount), # 只有生成支付宝链接时,不能用Decimal
  44. subject=subject,
  45. return_url=settings.RETURN_URL,
  46. notify_url=settings.NOTIFY_URL,
  47. )
  48. pay_url = iPay.gateway + \'?\' + order_string
  49. # 将支付链接存入,传递给views
  50. self.context[\'pay_url\'] = pay_url
  51. # 入库(两个表)的信息准备
  52. def _before_create(self, attrs, user, out_trade_no):
  53. attrs[\'user\'] = user
  54. attrs[\'out_trade_no\'] = out_trade_no
  55. def validate(self, attrs):
  56. # 1)订单总价校验
  57. total_amount = self._check_total_amount(attrs)
  58. # 2)生成订单号
  59. out_trade_no = self._get_out_trade_no()
  60. # 3)支付用户:request.user
  61. user = self._get_user()
  62. # 4)支付链接生成
  63. self._get_pay_url(out_trade_no, total_amount, attrs.get(\'subject\'))
  64. # 5)入库(两个表)的信息准备
  65. self._before_create(attrs, user, out_trade_no)
  66. # 代表该校验方法通过,进入入库操作
  67. return attrs
  68. # 重写入库方法的目的:完成订单与订单详情两个表入库操作
  69. def create(self, validated_data):
  70. courses = validated_data.pop(\'courses\')
  71. # 订单表入库,不需要courses
  72. order = models.Order.objects.create(**validated_data)
  73. # 订单详情表入库:只需要订单对象,课程对象(courses要拆成一个个course)
  74. for course in courses:
  75. models.OrderDetail.objects.create(order=order, course=course, price=course.price, real_price=course.price)
  76. # 先循环制造数据列表[{}, ..., {}],用群增完成入库 bulk_create(),效率高
  77. return order
  1. <template>
  2. ...
  3. <span class="buy-now" @click="buy_course(course)">立即购买</span>
  4. </template>
  5. <script>
  6. export default {
  7. methods: {
  8. // 购买课程
  9. buy_course(course) {
  10. // es6语法下,变量必须定义才能使用
  11. let token = this.$cookies.get(\'token\');
  12. if (!token) {
  13. this.$message({
  14. message: \'请先登录\',
  15. type: \'warning\'
  16. });
  17. return false
  18. }
  19. this.$axios({
  20. url: this.$settings.base_url + \'/order/pay/\',
  21. method: \'post\',
  22. headers: {
  23. authorization: \'luffy \' + token,
  24. },
  25. data: {
  26. subject: course.name,
  27. total_amount: course.price,
  28. pay_type: 1, // 现在只能默认1,为支付宝
  29. courses: [course.id] // 后台完成的是基于购物车情况下的接口,所以单购物为群购1个
  30. }
  31. }).then(response => {
  32. let pay_url = response.data;
  33. // console.log(pay_url)
  34. // 本页面标签调整:可以选择 _self 或 _blank
  35. open(pay_url, \'_self\');
  36. }).catch(error => {
  37. console.log(error.response.data)
  38. })
  39. },
  40. }
  41. }
  42. </script>
  1. import PaySuccess from \'../views/PaySuccess.vue\'
  2. // ...
  3. const routes = [
  4. // ...
  5. {
  6. path: \'/pay/success\',
  7. name: \'pay-success\',
  8. component: PaySuccess
  9. },
  10. ];
  1. `
  2. charset=utf-8&
  3. out_trade_no=7f7c7d12d57d45b693e1b49a6b01e1dd&
  4. method=alipay.trade.page.pay.return&
  5. total_amount=39.00&
  6. sign=FUmceqiNMWvxcD%2BUPCHiOTaEwlJ%2FXIXL5UwZWOSI1TwRjPIZVzjRLB4j2G5CQpn472JO8X%2BwMx04dHqjLxqLcY3TRu0XurQ%2FwKTNpyfDrtNuNv0rfGPuVHw52y3blbS7%2FKFVsWryw4%2BBuF2fCrJ4qWH8Zg14Rct7qoMbu73N74WkQtDyzXefiKDbkMMRMfLbelE9TFyeIeygeMId8%2B58mcJMUOh6aQqwpr9bzuBbfJ17fkqU%2F0ys9zGr%2FlDtLL7aAh6BPViqZN%2F9T7byCoferD1BhcSzJNR6V6VuhOdTq8iEaH2XgJT9aIiyHgg3GT1taBBvZX2gK41FSmkguk%2BfsA%3D%3D&
  7. trade_no=2020030722001464020500585462&
  8. auth_app_id=2016093000631831&
  9. version=1.0&
  10. app_id=2016093000631831&
  11. sign_type=RSA2&
  12. seller_id=2088102177958114&
  13. timestamp=2020-03-07%2014%3A47%3A48
  14. `
  15. // 同步回调没与订单状态
  1. <template>
  2. <div class="pay-success">
  3. <!--如果是单独的页面,就没必要展示导航栏(带有登录的用户)-->
  4. <Header/>
  5. <div class="main">
  6. <div class="title">
  7. <div class="success-tips">
  8. <p class="tips">您已成功购买 1 门课程!</p>
  9. </div>
  10. </div>
  11. <div class="order-info">
  12. <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p>
  13. <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p>
  14. <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p>
  15. </div>
  16. <div class="study">
  17. <span>立即学习</span>
  18. </div>
  19. </div>
  20. </div>
  21. </template>
  22. <script>
  23. import Header from "@/components/Header"
  24. export default {
  25. name: "Success",
  26. data() {
  27. return {
  28. result: {},
  29. };
  30. },
  31. created() {
  32. // url后拼接的参数:?及后面的所有参数 => ?a=1&b=2
  33. // console.log(location.search);
  34. // 解析支付宝回调的url参数
  35. let params = location.search.substring(1); // 去除? => a=1&b=2
  36. let items = params.length ? params.split(\'&\') : []; // [\'a=1\', \'b=2\']
  37. //逐个将每一项添加到args对象中
  38. for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2
  39. let k_v = items[i].split(\'=\'); // [\'a\', \'1\']
  40. //解码操作,因为查询字符串经过编码的
  41. if (k_v.length >= 2) {
  42. // url编码反解
  43. let k = decodeURIComponent(k_v[0]);
  44. this.result[k] = decodeURIComponent(k_v[1]);
  45. // 没有url编码反解
  46. // this.result[k_v[0]] = k_v[1];
  47. }
  48. }
  49. // 解析后的结果
  50. // console.log(this.result);
  51. // 把地址栏上面的支付结果,再get请求转发给后端
  52. this.$axios({
  53. url: this.$settings.base_url + \'/order/success/\' + location.search,
  54. method: \'get\',
  55. }).then(response => {
  56. console.log(response.data);
  57. }).catch(() => {
  58. console.log(\'支付结果同步失败\');
  59. })
  60. },
  61. components: {
  62. Header,
  63. }
  64. }
  65. </script>
  66. <style scoped>
  67. .main {
  68. padding: 60px 0;
  69. margin: 0 auto;
  70. width: 1200px;
  71. background: #fff;
  72. }
  73. .main .title {
  74. display: flex;
  75. -ms-flex-align: center;
  76. align-items: center;
  77. padding: 25px 40px;
  78. border-bottom: 1px solid #f2f2f2;
  79. }
  80. .main .title .success-tips {
  81. box-sizing: border-box;
  82. }
  83. .title img {
  84. vertical-align: middle;
  85. width: 60px;
  86. height: 60px;
  87. margin-right: 40px;
  88. }
  89. .title .success-tips {
  90. box-sizing: border-box;
  91. }
  92. .title .tips {
  93. font-size: 26px;
  94. color: #000;
  95. }
  96. .info span {
  97. color: #ec6730;
  98. }
  99. .order-info {
  100. padding: 25px 48px;
  101. padding-bottom: 15px;
  102. border-bottom: 1px solid #f2f2f2;
  103. }
  104. .order-info p {
  105. display: -ms-flexbox;
  106. display: flex;
  107. margin-bottom: 10px;
  108. font-size: 16px;
  109. }
  110. .order-info p b {
  111. font-weight: 400;
  112. color: #9d9d9d;
  113. white-space: nowrap;
  114. }
  115. .study {
  116. padding: 25px 40px;
  117. }
  118. .study span {
  119. display: block;
  120. width: 140px;
  121. height: 42px;
  122. text-align: center;
  123. line-height: 42px;
  124. cursor: pointer;
  125. background: #ffc210;
  126. border-radius: 6px;
  127. font-size: 16px;
  128. color: #fff;
  129. }
  130. </style>
  1. from utils.logging import logger
  2. # 支付回调接口
  3. class SuccessViewSet(ViewSet):
  4. authentication_classes = ()
  5. permission_classes = ()
  6. # 支付宝同步回调给前台,在同步通知给后台处理
  7. def get(self, request, *args, **kwargs):
  8. # return Response(\'后台已知晓,Over!!!\')
  9. # 不能在该接口完成订单修改操作
  10. # 但是可以在该接口中校验订单状态(已经收到支付宝post异步通知,订单已修改),告诉前台
  11. # print(type(request.query_params)) # django.http.request.QueryDict
  12. # print(type(request.query_params.dict())) # dict
  13. out_trade_no = request.query_params.get(\'out_trade_no\')
  14. try:
  15. models.Order.objects.get(out_trade_no=out_trade_no, order_status=1)
  16. return APIResponse(result=True)
  17. except:
  18. return APIResponse(1, \'error\', result=False)
  19. # 支付宝异步回调处理
  20. def post(self, request, *args, **kwargs):
  21. try:
  22. result_data = request.data.dict()
  23. out_trade_no = result_data.get(\'out_trade_no\')
  24. signature = result_data.pop(\'sign\')
  25. from libs import iPay
  26. result = iPay.alipay.verify(result_data, signature)
  27. if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
  28. # 完成订单修改:订单状态、流水号、支付时间
  29. models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1)
  30. # 完成日志记录
  31. logger.warning(\'%s订单支付成功\' % out_trade_no)
  32. return Response(\'success\')
  33. else:
  34. logger.error(\'%s订单支付失败\' % out_trade_no)
  35. except:
  36. pass
  37. return Response(\'failed\')

版权声明:本文为guyouyin123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/guyouyin123/p/12459398.html