前端赞赏功能弹框 要人家赞赏,首先得给人弄出打赏的弹框。前端 Article.vue 页面代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 <template> <div class="_21bLU4 _3kbg6I"> <Header></Header> <div class="_3VRLsv" role="main"> <div class="_gp-ck"> <section class="ouvJEz"> <h1 class="_1RuRku">{{ article_detail.name }}</h1> <div class="rEsl9f"> <div class="_2mYfmT"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img :src="article_detail.user.avatar" :alt="article_detail.user.nickname" class="_13D2Eh"/> </a> <div style="margin-left: 8px;"> <div class="_3U4Smb"> <span class="FxYr8x"><a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank">{{ article_detail.user.nickname }}</a></span> <button class="_3kba3h _1OyPqC _3Mi9q9 _34692-" data-locale="zh-CN" type="button"><span>关注</span> </button> </div> <div class="s-dsoj"> <time>{{ article_detail.updated_time|time_format }}</time> <span>字数 {{ article_detail.word_count }}</span> <span>阅读 {{ article_detail.read_count }}</span> </div> </div> </div> </div> <article class="_2rhmJa" v-html="article_detail.render"> </article> <div></div> <div class="_1kCBjS"> <div class="_18vaTa"> <div class="_3BUZPB"> <div aria-label="给文章点赞" class="_2Bo4Th" role="button" tabindex="-1"> <i aria-label="ic-like" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-like"></use> </svg> </i> </div> <span aria-label="查看点赞列表" class="_1LOh_5" role="button" tabindex="-1"> {{ article_detail.like_count }}人点赞 <i aria-label="icon: right" class="anticon anticon-right"> <svg aria-hidden="true" class="" data-icon="right" fill="currentColor" focusable="false" height="1em" viewbox="64 64 896 896" width="1em"> <path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path> </svg></i></span> </div> <div class="_3BUZPB"> <div class="_2Bo4Th" role="button" tabindex="-1"> <i aria-label="ic-dislike" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-dislike"></use> </svg> </i> </div> </div> </div> <div class="_18vaTa"> <a class="_3BUZPB _1x1ok9 _1OhGeD" href="/nb/38290018" rel="noopener noreferrer" target="_blank"><i aria-label="ic-notebook" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-notebook"></use> </svg> </i><span>随笔</span></a> <div class="_3BUZPB ant-dropdown-trigger"> <div class="_2Bo4Th"> <i aria-label="ic-others" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-others"></use> </svg> </i> </div> </div> </div> </div> <div class="_19DgIp" style="margin-top:24px;margin-bottom:24px"></div> <div class="_13lIbp"> <div class="_191KSt"> "小礼物走一走,来简书关注我" </div> <button class="_1OyPqC _3Mi9q9 _2WY0RL _1YbC5u" type="button" @click.stop="show_reward_window=true"> <span>赞赏支持</span> </button> <span class="_3zdmIj">还没有人赞赏,支持一下</span> </div> <div class="d0hShY"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img :alt="article_detail.user.nickname" class="_27NmgV" :src="article_detail.user.avatar"/> </a> <div class="Uz-vZq"> <div class="Cqpr1X"> <a class="HC3FFO _1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank" :title="article_detail.user.nickname">{{ article_detail.user.nickname }}</a> <span class="_2WEj6j" title="你读书的样子真好看。">你读书的样子真好看。</span> </div> <div class="lJvI3S"> <span>总资产0</span> <span>共写了78.7W字</span> <span>获得6,072个赞</span> <span>共1,308个粉丝</span> </div> </div> <button class="_1OyPqC _3Mi9q9" data-locale="zh-CN" type="button"><span>关注</span></button> </div> </section> <div id="note-page-comment"> <div class="lazyload-placeholder"></div> </div> </div> <aside class="_2OwGUo"> <section class="_3Z3nHf"> <div class="_3Oo-T1"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img alt="" class="_3T9iJQ" :src="article_detail.user.avatar"/></a> <div class="_32ZTTG"> <div class="_2O0T_w"> <div class="_2v-h3G"> <span class="_2vh4fr" :title="article_detail.user.nickname"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> {{ article_detail.user.nickname }} </a> </span> </div> <button class="tzrf9N _1OyPqC _3Mi9q9 _34692-" data-locale="zh-CN" type="button"><span>关注</span> </button> </div> <div class="_1pXc22"> 总资产0 </div> </div> </div> <div class="_19DgIp"></div> </section> <div> <div class=""> <section class="_3Z3nHf"> <h3 class="QHRnq8 QxT4hD"><span>推荐阅读</span></h3> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="这些话没人告诉你,但必须知道的社会规则"> <a class="_1-HJSV _1OhGeD" href="/p/a3e56a0559ff" rel="noopener noreferrer" target="_blank">这些话没人告诉你,但必须知道的社会规则</a> </div> <div class="_19haGh"> 阅读 5,837 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="浙大学霸最美笔记曝光:真正的牛人,都“变态”到了极致"> <a class="_1-HJSV _1OhGeD" href="/p/d2a3724e2839" rel="noopener noreferrer" target="_blank">浙大学霸最美笔记曝光:真正的牛人,都“变态”到了极致</a> </div> <div class="_19haGh"> 阅读 12,447 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="征服一个女人最好的方式:不是讨好她,而是懂得去折腾她"> <a class="_1-HJSV _1OhGeD" href="/p/f6acf67f039b" rel="noopener noreferrer" target="_blank">征服一个女人最好的方式:不是讨好她,而是懂得去折腾她</a> </div> <div class="_19haGh"> 阅读 5,311 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="告别平庸的15个小方法"> <a class="_1-HJSV _1OhGeD" href="/p/cff7eb6b232b" rel="noopener noreferrer" target="_blank">告别平庸的15个小方法</a> </div> <div class="_19haGh"> 阅读 7,040 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="轻微抑郁的人,会说这3句“口头禅”,若你一个不占,偷着乐吧"> <a class="_1-HJSV _1OhGeD" href="/p/2a0ca1729b4b" rel="noopener noreferrer" target="_blank">轻微抑郁的人,会说这3句“口头禅”,若你一个不占,偷着乐吧</a> </div> <div class="_19haGh"> 阅读 16,411 </div> </div> </section> </div> </div> </aside> </div> <div class="_23ISFX-body" v-if="show_reward_window" @click.stop="show_reward_window=true"> <div class="_3uZ5OL"> <div class="_2PLkjk"> <img class="_2R1-48" src="https://upload.jianshu.io/users/upload_avatars/9602437/8fb37921-2e4f-42a7-8568-63f187c5721b.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/100/h/100/format/webp" alt=""/> <div class="_2h5tnQ"> 给作者送糖 </div> </div> <div class="_1-bCJJ"> <div class="LMa6S_" :class="reward_info.money===num?'_1vONvL':''" @click="reward_info.money=num" v-for="num in reward_list"><span>{{num}}</span></div> </div> <textarea class="_1yN79W" placeholder="给Ta留言..."></textarea> <div class="_1_B577"> 选择支付方式 </div> <div class="_1-bCJJ"> <div class="LMa6S_ _3PA8BN" :class="{'_1vONvL': reward_info.pay_type===type}" @click="reward_info.pay_type=type" v-for="type in pay_type_list"><span>{{type}}</span></div> </div> <button type="button" class="_3A-4KL _1OyPqC _3Mi9q9 _1YbC5u"> <span>确认支付</span><span> ¥</span>{{reward_info.money}} </button> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header"; import Footer from "./common/Footer"; export default { name: "Article", components: { Header, Footer, }, data() { return { article_id: 0, token: '', article_detail: { user: {} }, show_reward_window: false, reward_list: [2, 5, 10, 20, 50, 100], pay_type_list: ['支付宝', '账户余额'], reward_info: { 'money': this.reward_list[0], 'pay_type': '支付宝' }, } }, filters: { time_format(time) { let t = new Date(time); return `${t.getFullYear()}.${t.getMonth() + 1}.${t.getDate()} ${t.getHours()}:${t.getMinutes()}`; }, }, methods: { get_article_detail() { this.$axios.get(`${this.$settings.Host}/article/detail/${this.article_id}/`).then(response => { this.article_detail = response.data; }).catch(errors => { this.$message.error('获取文章详情信息失败!') }) }, }, created() { this.article_id = this.$route.params.pk; this.token = localStorage.user_token || sessionStorage.user_token; this.get_article_detail(); }, mounted() { document.onclick = () => { this.show_reward_window = false; } } } </script>
第三方支付接口 第三方支付接口,可以实现网络转账。
常见的第三方支付接口有很多:
国外:万事达,applePay,PayPal,Visa,八达通,西联(邮政汇款)
国内:支付宝,微信,京东钱包,百度钱包,贝宝(PayPal 中国版)
支付宝支付接口 支付宝开放平台登录 支付宝开放平台官网:https://open.alipay.com/platform/home.htm
使用支付宝账号登录即可。如果是第一次登录,可能会要求等级一些信息,如是填写就好。
支付宝的申请需要企业资质,但是我们作为开发者可以使用支付宝提供的测试账号先开发功能,将来调整账号即可用于公司项目的正式运营。
地址:https://openhome.alipay.com/platform/developerIndex.htm
沙箱环境
1 2 真实的支付宝网关: https:// openapi.alipay.com/gateway.do 沙箱的支付宝网关: https:// openapi.alipaydev.com/gateway.do
支付宝开发者文档
电脑网站支付流程 【前后端不分离】时序图(时间顺序流程图 )
【前后端分离】时序图
RSA 算法,属于非对称加密,一旦加密以后不能解密的。 可以通过密钥来进行验证。 密钥成对生成的。分公钥和私钥。 公钥用于验证数据(解签) 私钥用于加密数据(签名)
开发支付功能 首先创建专门用于第三方支付的 payments 子应用:
1 2 cd renranapi/appspython ../../manage.py startapp payments
注册子应用
1 2 3 4 INSTALLED_APPS = [ ... 'payments' , ]
配置秘钥 生成应用的私钥和公钥 下载对应系统的秘钥生成工具:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1
Windows 操作系统 生成如下,安装软件时需要管理员身份来安装。
Linux 系统 生成方法如下:
1 2 3 4 5 6 7 cd renranapi/apps/payments/mkdir keys cd keysopenssl OpenSSL> genrsa -out app_private_key.pem 2048 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem OpenSSL> exit
先要注册应用,然后设置接口加签方式。
应用公钥复制粘贴到支付宝网站页面中。点击修改以后,粘贴进去。
如果是使用沙箱测试,则需要在沙箱应用处配置应用公钥,从而获取支付宝公钥。
保存应用私钥文件 在 payments 应用中新建 keys 目录,用来保存秘钥文件。
将应用私钥文件 app_private_key.pem 复制到 payment/keys 目录下。
Windows 系统生成的私钥必须在上下两行加上以下标识:
1 2 3 -----BEGIN RSA PRIVATE KEY ----- 私钥 -----END RSA PRIVATE KEY -----
保存支付宝公钥到项目中 在 payments/key 目录下新建 alipay_public_key.pem 文件,用于保存支付宝的公钥文件。
将支付宝的公钥内容复制到 alipay_public_key.pem 文件中
1 2 3 -----BEGIN PUBLIC KEY ----- 公钥 -----END PUBLIC KEY -----
使用支付宝的 sdk 开发支付接口 SDK:https://docs.open.alipay.com/270/106291/
Python 版本的支付宝 SDK 文档:/project/https:/github.com/fzlee/alipay/blob/master/README.zh-hans
安装命令:
1 pip install python-alipay-sdk
后端提供发起支付的接口 url 地址 用户模型新增 money 字段,表示作者用户接收别人打赏的资金资产。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class User (AbstractUser ): """用户模型类""" mobile = models.CharField(max_length=15 , null=True , unique=True , help_text="手机号码" , verbose_name="手机号码" ) wechat = models.CharField(max_length=100 , null=True , unique=True , help_text="微信账号" , verbose_name="微信账号" ) alipay = models.CharField(max_length=100 , null=True , unique=True , help_text="支付宝账号" , verbose_name="支付宝账号" ) qq_number = models.CharField(max_length=11 , null=True , unique=True , help_text="QQ号" , verbose_name="QQ号" ) avatar = models.ImageField(upload_to="avatar" , null=True , default=None , verbose_name="头像" ) nickname = models.CharField(max_length=100 , null=True , default=None , verbose_name="用户昵称" ) money = models.DecimalField(max_digits=11 , decimal_places=2 , default=0 , verbose_name="资金" ) class Meta : db_table = "rr_users" verbose_name = "用户信息" verbose_name_plural = verbose_name def __str__ (self ): return self.username
在 payments/models.py
中创建模型,保存打赏记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from django.db import modelsfrom users.models import Userfrom article.models import Articlefrom renranapi.utils.models import BaseModelclass Reward (BaseModel ): REWARD_OPT = ( (0 , "支付宝" ), (1 , "余额" ), ) STATUS_OPT = ( (0 , "未付款" ), (1 , "已付款" ), (2 , "已取消" ), (3 , "超时取消" ), ) user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="打赏用户" ) money = models.DecimalField(decimal_places=2 , max_digits=6 , verbose_name="打赏金额" ) article = models.ForeignKey(Article, on_delete=models.DO_NOTHING, verbose_name="文章" ) status = models.SmallIntegerField(default=0 ,choices=STATUS_OPT, verbose_name="打赏状态" ) trade_no = models.CharField(max_length=255 , null=True , blank=True , verbose_name="流水号" ) out_trade_no = models.CharField(max_length=255 , null=True , blank=True , verbose_name="支付平台返回的流水号" ) reward_type = models.SmallIntegerField(default=0 ,choices=REWARD_OPT, verbose_name="打赏类型" ) message = models.TextField(null=True ,blank=True , verbose_name="打赏留言" ) class Meta : db_table = "rr_reward" verbose_name = "打赏记录" verbose_name_plural = verbose_name def __str__ (self ): return f'{self.user.nickname} 向{self.article.user.nickname} 的文章《{self.article.name} 》打赏了{self.money} 元'
迁移迁移
1 2 python manage.py makemigrations python manage.py migrate
注册模型到 xadmin 中,创建 adminx.py
,代码
1 2 3 4 5 6 import xadminfrom .models import Rewardclass RewardModelAdmin (object ): pass xadmin.site.register(Reward,RewardModelAdmin)
编写视图提供支付的 url 跳转链接地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 import randomfrom datetime import datetimefrom alipay import AliPayfrom django.conf import settingsfrom rest_framework import statusfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework.response import Responsefrom rest_framework.viewsets import ViewSetfrom . import modelsfrom article.models import Articleclass AlipayAPIViewSet (ViewSet ): permission_classes = [IsAuthenticated] def post (self, request ): """生成打赏链接,并创建打赏记录""" user = request.user reward_type = request.data.get('pay_type' ) message = request.data.get('content' ) money = request.data.get('money' ) if money <= 0 : return Response({'error_msg' : '打赏金额应该大于 0!' }, status=status.HTTP_400_BAD_REQUEST) article_id = request.data.get('article_id' ) try : Article.objects.get(id =article_id, is_public=True ) except Article.DoesNotExist: return Response({'error_msg' : '文章不存在或尚未发布,无法打赏' }, status=status.HTTP_400_BAD_REQUEST) if Article.object .filter (user=user, id =article_id): return Response({'error_mes' : '不能给自己的文章打赏!' }, status=status.HTTP_400_BAD_REQUEST) while 1 : trade_no = f'{datetime.now().strftime("%Y%m%d%H%M%S" )} {"%06d" % user.id } {"%06d" % random.randint(1 , 999999 )} ' try : models.Reward.objects.get(trade_no=trade_no) except models.Reward.DoesNotExist: break reward = models.Reward.objects.create( user=user, money=money, article_id=article_id, status=0 , trade_no=trade_no, out_trade_no=None , reward_type=reward_type, message=message, orders=0 , ) if reward_type == 0 : with open (settings.ALIAPY_CONFIG["app_private_key_path" ]) as fh: app_private_key_string = fh.read() with open (settings.ALIAPY_CONFIG["alipay_public_key_path" ]) as fh: alipay_public_key_string = fh.read() alipay = AliPay( appid=settings.ALIAPY_CONFIG["appid" ], app_notify_url=settings.ALIAPY_CONFIG["app_notify_url" ], app_private_key_string=app_private_key_string, alipay_public_key_string=alipay_public_key_string, sign_type=settings.ALIAPY_CONFIG["sign_type" ], debug=settings.ALIAPY_CONFIG["debug" ] ) order_string = alipay.api_alipay_trade_page_pay( out_trade_no=reward.trade_no, total_amount=float (reward.money), subject="打赏文章" , return_url=settings.ALIAPY_CONFIG["return_url" ], notify_url=settings.ALIAPY_CONFIG["notify_url" ] ) url = settings.ALIAPY_CONFIG["gateway_url" ] + order_string else : url = '' return Response(url)
在配置文件中编辑支付宝的配置信息(实际的值根据自己的账号而定) 在 setttins/dev.py
中添加支付宝接口相关的配置代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 ALIAPY_CONFIG = { "gateway_url" : "https://openapi.alipaydev.com/gateway.do?" , "appid" : "2016101900722372" , "app_notify_url" : None , "app_private_key_path" : os.path.join(BASE_DIR, "apps/payments/keys/app_private_key.pem" ), "alipay_public_key_path" : os.path.join(BASE_DIR, "apps/payments/keys/alipay_public_key.pem" ), "sign_type" : "RSA2" , "debug" : False , "return_url" : "http://www.moluo.net:8080/wallet" , "notify_url" : "http://api.renran.cn:8000/payments/alipay/result/" , }
注册 url 地址,payments/urls.py
代码:
1 2 3 4 5 from django.urls import path,re_pathfrom . import viewsurlpatterns = [ path('alipay/' , views.AlipayAPIViewSet.as_view({'post' : 'post' })), ]
总路由,代码:
1 path('payments/' , include('payments.urls' )),
客户端实现点击打赏请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 <template> <div class="_21bLU4 _3kbg6I"> <Header></Header> <div class="_3VRLsv" role="main"> <div class="_gp-ck"> <section class="ouvJEz"> <h1 class="_1RuRku">{{ article_detail.name }}</h1> <div class="rEsl9f"> <div class="_2mYfmT"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img :src="article_detail.user.avatar" :alt="article_detail.user.nickname" class="_13D2Eh"/> </a> <div style="margin-left: 8px;"> <div class="_3U4Smb"> <span class="FxYr8x"><a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank">{{ article_detail.user.nickname }}</a></span> <button class="_3kba3h _1OyPqC _3Mi9q9 _34692-" data-locale="zh-CN" type="button"><span>关注</span> </button> </div> <div class="s-dsoj"> <time>{{ article_detail.updated_time|time_format }}</time> <span>字数 {{ article_detail.word_count }}</span> <span>阅读 {{ article_detail.read_count }}</span> </div> </div> </div> </div> <article class="_2rhmJa" v-html="article_detail.render"> </article> <div></div> <div class="_1kCBjS"> <div class="_18vaTa"> <div class="_3BUZPB"> <div aria-label="给文章点赞" class="_2Bo4Th" role="button" tabindex="-1"> <i aria-label="ic-like" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-like"></use> </svg> </i> </div> <span aria-label="查看点赞列表" class="_1LOh_5" role="button" tabindex="-1"> {{ article_detail.like_count }}人点赞 <i aria-label="icon: right" class="anticon anticon-right"> <svg aria-hidden="true" class="" data-icon="right" fill="currentColor" focusable="false" height="1em" viewbox="64 64 896 896" width="1em"> <path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path> </svg></i></span> </div> <div class="_3BUZPB"> <div class="_2Bo4Th" role="button" tabindex="-1"> <i aria-label="ic-dislike" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-dislike"></use> </svg> </i> </div> </div> </div> <div class="_18vaTa"> <a class="_3BUZPB _1x1ok9 _1OhGeD" href="/nb/38290018" rel="noopener noreferrer" target="_blank"><i aria-label="ic-notebook" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-notebook"></use> </svg> </i><span>随笔</span></a> <div class="_3BUZPB ant-dropdown-trigger"> <div class="_2Bo4Th"> <i aria-label="ic-others" class="anticon"> <svg aria-hidden="true" class="" fill="currentColor" focusable="false" height="1em" width="1em"> <use xlink:href="#ic-others"></use> </svg> </i> </div> </div> </div> </div> <div class="_19DgIp" style="margin-top:24px;margin-bottom:24px"></div> <div class="_13lIbp"> <div class="_191KSt"> "小礼物走一走,来简书关注我" </div> <button class="_1OyPqC _3Mi9q9 _2WY0RL _1YbC5u" type="button" @click.stop="article_reward"> <span>赞赏支持</span> </button> <span class="_3zdmIj">还没有人赞赏,支持一下</span> </div> <div class="d0hShY"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img :alt="article_detail.user.nickname" class="_27NmgV" :src="article_detail.user.avatar"/> </a> <div class="Uz-vZq"> <div class="Cqpr1X"> <a class="HC3FFO _1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank" :title="article_detail.user.nickname">{{ article_detail.user.nickname }}</a> <span class="_2WEj6j" title="你读书的样子真好看。">你读书的样子真好看。</span> </div> <div class="lJvI3S"> <span>总资产0</span> <span>共写了78.7W字</span> <span>获得6,072个赞</span> <span>共1,308个粉丝</span> </div> </div> <button class="_1OyPqC _3Mi9q9" data-locale="zh-CN" type="button"><span>关注</span></button> </div> </section> <div id="note-page-comment"> <div class="lazyload-placeholder"></div> </div> </div> <aside class="_2OwGUo"> <section class="_3Z3nHf"> <div class="_3Oo-T1"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> <img alt="" class="_3T9iJQ" :src="article_detail.user.avatar"/></a> <div class="_32ZTTG"> <div class="_2O0T_w"> <div class="_2v-h3G"> <span class="_2vh4fr" :title="article_detail.user.nickname"> <a class="_1OhGeD" href="/u/a70487cda447" rel="noopener noreferrer" target="_blank"> {{ article_detail.user.nickname }} </a> </span> </div> <button class="tzrf9N _1OyPqC _3Mi9q9 _34692-" data-locale="zh-CN" type="button"><span>关注</span> </button> </div> <div class="_1pXc22"> 总资产0 </div> </div> </div> <div class="_19DgIp"></div> </section> <div> <div class=""> <section class="_3Z3nHf"> <h3 class="QHRnq8 QxT4hD"><span>推荐阅读</span></h3> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="这些话没人告诉你,但必须知道的社会规则"> <a class="_1-HJSV _1OhGeD" href="/p/a3e56a0559ff" rel="noopener noreferrer" target="_blank">这些话没人告诉你,但必须知道的社会规则</a> </div> <div class="_19haGh"> 阅读 5,837 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="浙大学霸最美笔记曝光:真正的牛人,都“变态”到了极致"> <a class="_1-HJSV _1OhGeD" href="/p/d2a3724e2839" rel="noopener noreferrer" target="_blank">浙大学霸最美笔记曝光:真正的牛人,都“变态”到了极致</a> </div> <div class="_19haGh"> 阅读 12,447 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="征服一个女人最好的方式:不是讨好她,而是懂得去折腾她"> <a class="_1-HJSV _1OhGeD" href="/p/f6acf67f039b" rel="noopener noreferrer" target="_blank">征服一个女人最好的方式:不是讨好她,而是懂得去折腾她</a> </div> <div class="_19haGh"> 阅读 5,311 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="告别平庸的15个小方法"> <a class="_1-HJSV _1OhGeD" href="/p/cff7eb6b232b" rel="noopener noreferrer" target="_blank">告别平庸的15个小方法</a> </div> <div class="_19haGh"> 阅读 7,040 </div> </div> <div class="cuOxAY" role="listitem"> <div class="_3L5YSq" title="轻微抑郁的人,会说这3句“口头禅”,若你一个不占,偷着乐吧"> <a class="_1-HJSV _1OhGeD" href="/p/2a0ca1729b4b" rel="noopener noreferrer" target="_blank">轻微抑郁的人,会说这3句“口头禅”,若你一个不占,偷着乐吧</a> </div> <div class="_19haGh"> 阅读 16,411 </div> </div> </section> </div> </div> </aside> </div> <div class="_23ISFX-body" v-if="show_reward_window" @click.stop="show_reward_window=true"> <div class="_3uZ5OL"> <div class="_2PLkjk"> <img class="_2R1-48" src="https://upload.jianshu.io/users/upload_avatars/9602437/8fb37921-2e4f-42a7-8568-63f187c5721b.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/100/h/100/format/webp" alt=""/> <div class="_2h5tnQ"> 给作者送糖 </div> </div> <div class="_1-bCJJ"> <div class="LMa6S_" :class="reward_info.money===num?'_1vONvL':''" @click="reward_info.money=num" v-for="num in reward_list"><span>{{num}}</span></div> </div> <textarea class="_1yN79W" placeholder="给Ta留言..." v-model="reward_msg"></textarea> <div class="_1_B577"> 选择支付方式 </div> <div class="_1-bCJJ"> <div class="LMa6S_ _3PA8BN" :class="{'_1vONvL': reward_info.pay_type===key}" @click="reward_info.pay_type=key" v-for="type,key in pay_type_list" :key="key"> <span>{{type}}</span> </div> </div> <button type="button" class="_3A-4KL _1OyPqC _3Mi9q9 _1YbC5u" @click="confirm_payment"> <span>确认支付</span><span> ¥</span>{{reward_info.money}} </button> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header"; import Footer from "./common/Footer"; export default { name: "Article", components: { Header, Footer, }, data() { return { article_id: 0, token: '', article_detail: { user: {} }, show_reward_window: false, reward_list: [2, 5, 10, 20, 50, 100], pay_type_list: ['支付宝', '账户余额'], reward_info: { 'money': 2, 'pay_type': 0 }, reward_msg: '', } }, filters: { time_format(time) { let t = new Date(time); return `${t.getFullYear()}.${t.getMonth() + 1}.${t.getDate()} ${t.getHours()}:${t.getMinutes()}`; }, }, methods: { get_article_detail () { this.$axios.get(`${this.$settings.Host}/article/detail/${this.article_id}/`).then(response => { this.article_detail = response.data; }).catch(errors => { this.$message.error('获取文章详情信息失败!') }) }, article_reward () { this.$settings.check_login(this); this.show_reward_window=true; }, confirm_payment () { this.$axios.post(`${this.$settings.Host}/payments/alipay/`, { article_id: this.article_id, pay_type: this.reward_info.pay_type, money: this.reward_info.money, message: this.reward_msg, }, { headers: { Authorization: `jwt ${this.token}` } }).then(response=>{ location.href = response.data }).catch(errors=>{ this.$message.error('获取支付宝链接失败!') }) }, }, created() { this.article_id = this.$route.params.pk; this.token = localStorage.user_token || sessionStorage.user_token; this.get_article_detail(); }, mounted() { document.onclick = () => { this.show_reward_window = false; } } } </script>
用户支付完成以后的支付结果处理 客户端接收支付宝跳转发送回来的同步结果参数,并发起请求服务端的同步处理结果的 API 接口。
首先创建前端页面组件,Wallet.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <template> </template> <script> export default { name: "Wallet", data () { return { token: '', } }, created() { this.token = this.$settings.check_login(this); // 把支付宝的同步结果通知转发给服务端 this.get_pay_result(); }, methods: { get_pay_result() { // 判断是否是从支付页面返回 if(this.$route.query.out_trade_no) { // 将结果返回给后台 this.$axios.get(`${this.$settings.Host}/payments/alipay/result/${location.search}`, { headers: { Authorization: `jwt ${this.token}` } }).then(response=>{ this.$router.push('/') }).catch(errors=>{ this.$router.push('/') }) } } } } </script> <style scoped> </style>
路由代码:
1 2 3 4 5 6 7 8 9 10 11 import Wallet from "../components/Wallet" ;export default new Router({ ... { path : '/wallet' , name : 'Wallet' , component : Wallet, }, ] })
服务端完成同步支付结果和异步通知的处理 用户付过钱之后,会跳转到回调页面,也就是 wallet。url 中会携带付款情况的各种参数。前端 wallet 页面会把这些参数传给后端,我们就在这里处理一下。除了前端会有支付结果返回外,支付宝还会给我们的后端发送异步通知。这两个通知的数据是一样的,只不过一个是 get 请求,一个是 post 请求,在获取数据的时候稍微处理一下即可。
首先是在视图中,增加一个 return_result 方法,用来处理前端发送过来的通知参数和支付宝给我们后端发送的通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 import randomfrom datetime import datetimefrom alipay import AliPayfrom django.conf import settingsfrom django.db import transactionfrom rest_framework import statusfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework.response import Responsefrom rest_framework.viewsets import ViewSetfrom . import modelsfrom article.models import Articleclass AlipayAPIViewSet (ViewSet ): permission_classes = [IsAuthenticated] def get_alipay (self ): with open (settings.ALIAPY_CONFIG["app_private_key_path" ]) as fh: app_private_key_string = fh.read() with open (settings.ALIAPY_CONFIG["alipay_public_key_path" ]) as fh: alipay_public_key_string = fh.read() alipay = AliPay( appid=settings.ALIAPY_CONFIG["appid" ], app_notify_url=settings.ALIAPY_CONFIG["app_notify_url" ], app_private_key_string=app_private_key_string, alipay_public_key_string=alipay_public_key_string, sign_type=settings.ALIAPY_CONFIG["sign_type" ], debug=settings.ALIAPY_CONFIG["debug" ] ) return alipay def post (self, request ): """生成打赏链接,并创建打赏记录""" user = request.user reward_type = request.data.get('pay_type' ) message = request.data.get('content' ) money = request.data.get('money' ) if money <= 0 : return Response({'error_msg' : '打赏金额应该大于 0!' }, status=status.HTTP_400_BAD_REQUEST) article_id = request.data.get('article_id' ) try : Article.objects.get(id =article_id, is_public=True ) except Article.DoesNotExist: return Response({'error_msg' : '文章不存在或尚未发布,无法打赏' }, status=status.HTTP_400_BAD_REQUEST) if Article.objects.filter (user=user, id =article_id): return Response({'error_mes' : '不能给自己的文章打赏!' }, status=status.HTTP_400_BAD_REQUEST) while 1 : trade_no = f'{datetime.now().strftime("%Y%m%d%H%M%S" )} {"%06d" % user.id } {"%06d" % random.randint(1 , 999999 )} ' try : models.Reward.objects.get(trade_no=trade_no) except models.Reward.DoesNotExist: break reward = models.Reward.objects.create( user=user, money=money, article_id=article_id, status=0 , trade_no=trade_no, out_trade_no=None , reward_type=reward_type, message=message, orders=0 , ) if reward_type == 0 : alipay = self.get_alipay() order_string = alipay.api_alipay_trade_page_pay( out_trade_no=reward.trade_no, total_amount=float (reward.money), subject="打赏文章" , return_url=settings.ALIAPY_CONFIG["return_url" ], notify_url=settings.ALIAPY_CONFIG["notify_url" ] ) url = settings.ALIAPY_CONFIG["gateway_url" ] + order_string else : url = '' return Response(url) def return_result (self, request ): """支付宝同步结果和异步通知处理""" data = request.query_params.dict () or request.data.dict () signature = data.pop('sign' ) alipay = self.get_alipay() success = alipay.verify(data, signature) if success: """支付结果处理""" with transaction.atomic(): save_point = transaction.savepoint() try : reward = models.Reward.objects.get(trade_no=data.get('out_trade_no' ), status=0 ) reward.status = 1 reward.save() article = reward.article article.reward_count += 1 article.save() author = article.user author.money = round (author.money + reward.money, 2 ) author.save() except : transaction.savepoint_rollback(save_point) return Response({'error_msg' : '支付有误!' }, status=status.HTTP_400_BAD_REQUEST) else : return Response({'error_msg' : '支付有误!' }, status=status.HTTP_400_BAD_REQUEST)
路由代码:
1 2 3 4 5 6 from django.urls import path,re_pathfrom . import viewsurlpatterns = [ path("alipay/" , views.AliPayAPIViewSet.as_view({"post" :"post" })), path("alipay/result/" , views.AliPayAPIViewSet.as_view({"get" :"return_result" ,"post" :"return_result" })), ]
支付宝功能就此实现,使用支付宝沙箱账号可以进行测试。
因为支付宝的异步通知是要发送请求给域名,需要在服务器中才能测试。