危险

为之则易,不为则难

0%

13_支付功能

今日目标

✔ 支付功能。

支付-支付页面-基础布局

目的:配置路由和支付页面基础布局。

image-20220302233840405

  1. 新建文件 src/views/member/pay/index.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
<script name="XtxPayPage" setup lang="ts"></script>
<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付订单</XtxBreadItem>
</XtxBread>
<!-- 付款信息 -->
<div class="pay-info">
<span class="icon iconfont icon-queren2"></span>
<div class="tip">
<p>订单提交成功!请尽快完成支付。</p>
<p>支付还剩 <span>24分59秒</span>, 超时后将取消订单</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>¥5673.00</span>
</div>
</div>
<!-- 付款方式 -->
<div class="pay-type">
<p class="head">选择以下支付方式付款</p>
<div class="item">
<p>支付平台</p>
<a class="btn wx" href="javascript:;"></a>
<a class="btn alipay" href="javascript:;"></a>
</div>
<div class="item">
<p>支付方式</p>
<a class="btn" href="javascript:;">招商银行</a>
<a class="btn" href="javascript:;">工商银行</a>
<a class="btn" href="javascript:;">建设银行</a>
<a class="btn" href="javascript:;">农业银行</a>
<a class="btn" href="javascript:;">交通银行</a>
</div>
</div>
</div>
</div>
</template>

<style scoped lang="less">
.pay-info {
background: #fff;
display: flex;
align-items: center;
height: 240px;
padding: 0 80px;
.icon {
font-size: 80px;
color: #1dc779;
}
.tip {
padding-left: 10px;
flex: 1;
p {
&:first-child {
font-size: 20px;
margin-bottom: 5px;
}
&:last-child {
color: #999;
font-size: 16px;
}
}
}
.amount {
span {
&:first-child {
font-size: 16px;
color: #999;
}
&:last-child {
color: @priceColor;
font-size: 20px;
}
}
}
}
.pay-type {
margin-top: 20px;
background-color: #fff;
padding-bottom: 70px;
p {
line-height: 70px;
height: 70px;
padding-left: 30px;
font-size: 16px;
&.head {
border-bottom: 1px solid #f5f5f5;
}
}
.btn {
width: 150px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-left: 30px;
color: #666666;
display: inline-block;
&.active,
&:hover {
border-color: @xtxColor;
}
&.alipay {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png) no-repeat center / contain;
}
&.wx {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg) no-repeat center / contain;
}
}
}
</style>
  1. src/router/index.ts
1
2
3
4
{
path: '/member/pay',
component: () => import('@/views/member/pay/index.vue')
}

订单跳转页

本节目标:订单跳转并传递订单 id 参数。

页面跳转思考

  • router.push()router.replace() 的区别是什么?

    • push 追加,后退回到前页面

    • replace 替换,后退不能回到前页面

  • 提交订单后跳转到 支付详情页,是通过 push 合适,还是用 replace 更合适呢,为什么?

    • replace 更合适,下单后商品信息已经没有了,后退回去无意义。
  • 跳转时传递订单 id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const submitCheckout = async () => {
// 如果地址为空,不能提交订单
if (!checkout.showAddress) {
return Message.warning('请选择收货地址')
}
const res = await request.post('/member/order', {
goods: checkout.checkoutInfo.goods.map((item) => {
return {
skuId: item.skuId,
count: item.count,
}
}),
addressId: checkout.showAddress.id,
})
// 成功提醒用户
Message({ type: 'success', text: '下单成功~' })
// 🔔重新获取购物车列表
+ cart.getCartList()
// 跳转到支付页面

+ router.replace('/member/pay?id=' + res.data.result.id)
}

支付数据渲染

任务目标: 渲染真实支付数据

  1. 提供数据类型。
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
export interface OrderPayInfo {
id: string
createTime: string
payType: number
orderState: number
payLatestTime: string
countdown: number
postFee: number
payMoney: number
payChannel: number
totalMoney: number
totalNum: number
deliveryTimeType: number
receiverContact: string
receiverMobile: string
provinceCode: string
cityCode: string
countyCode: string
receiverAddress: string
payTime?: any
consignTime?: any
endTime?: any
closeTime?: any
evaluationTime?: any
skus: {
id: string
spuId: string
name: string
quantity: number
image: string
realPay: number
curPrice: number
totalMoney?: any
properties: {
propertyMainName: string
propertyValueName: string
}[]
attrsText: string
}[]
arrivalEstimatedTime?: any
}
  1. 发送请求获取支付数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script name="XtxPayPage" setup lang="ts">
import { ApiRes } from '@/types/data'
import request from '@/utils/request'
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { OrderPayInfo } from '@/types/order'

const route = useRoute()
const id = route.query.id
const order = ref<OrderPayInfo>({} as OrderPayInfo)
onMounted(async () => {
const res = await request.get<ApiRes<OrderPayInfo>>(`/member/order/${id}`)
order.value = res.data.result
})
</script>
  1. 渲染支付信息。
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 付款信息 -->
<div class="pay-info">
<span class="icon iconfont icon-queren2"></span>
<div class="tip">
<p>订单提交成功!请尽快完成支付。</p>
<p>支付还剩 <span>{{ order?.countdown }}</span>, 超时后将取消订单</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>¥{{ order?.totalMoney }}</span>
</div>
</div>

倒计时渲染

  1. 开启倒计时。
1
2
3
4
5
6
7
8
9
10
11
12
const showTime = ref(0)

onMounted(async () => {
const res = await request.get<ApiRes<OrderPayInfo>>(`/member/order/${id}`)
console.log('GET', `/member/order/${id}`, res.data.result)
order.value = res.data.result
const { time, start } = useCountDown(order.value.countdown)
start()
watch(time, (value) => {
showTime.value = value
})
})
  1. 安装 dayjs。
1
yarn add dayjs
  1. 提供方法(注意:vue3 没有过滤器)。
1
2
3
const formatTime = (time: number) => {
return dayjs.unix(time).format('mm分ss秒')
}
  1. 渲染。
1
2
3
<p>
支付还剩 <span>{{ formatTime(showTime) }} </span>, 超时后将取消订单
</p>
  1. 倒计时为 0,重新跳转到购物车页面。
1
2
3
4
5
6
7
8
9
10
11
12
// 将初始值, 同步给showTime
showTime.value = order.value.countdown

watch(time, (value) => {
showTime.value = value

if (value <= 0) {
// 跳转页面, 并提醒用户, 订单已超时
router.replace('/cart')
Message.warning('订单已超时, 请重新下单')
}
})

支付-支付流程

目的:知道小兔鲜支付流程。

image-20220303065836123

总结:

  • PC 前台点击支付按钮,新开标签页打开后台提供的支付链接带上订单 ID 还有回跳地址。

  • 后台服务发起支付,等待支付结果,修改订单状态,回跳 PC 前台结果页。

  • PC 前台在结果页获取回跳 URL 参数订单 ID 查询支付状态,展示支付结果。

1
2
3
4
5
# 支付地址回调地址(可变)
http://www.corho.com:8080/#/pay/callback
买家账号jfjbwb4477@sandbox.com
登录密码111111
支付密码111111

支付-等待支付

目的:支付打开新页,当前页打开提示框。

  1. utils/request.ts 中导出基础路径。
1
2
3
4
5
6
export const baseURL = 'https://apipc-xiaotuxian-front.itheima.net/'
const request = axios.create({
baseURL,
// baseURL: 'http://pcapi-xiaotuxian-front.itheima.net/',
timeout: 5000,
})
  1. 拼接支付链接。
1
2
const redirectUrl = encodeURIComponent('http://www.corho.com:8080/#/pay/callback')
const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.id}&redirect=${redirectUrl}`
  1. 增加跳转链接。
1
<a class="btn alipay" :href="payUrl"></a>

支付结果展示

任务目标: 对支付完的结果进行展示

image-20220303070139340

实现步骤

  1. 准备一个基础页面。

  2. 根据地址订单 ID 查询订单状态进行展示,或者是地址栏支付结果。

代码落地

  1. 准备结果页面 src/views/pay/callback.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
<script setup lang="ts">
//
</script>

<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付结果</XtxBreadItem>
</XtxBread>
<!-- 支付结果 -->
<div class="pay-result">
<span class="iconfont icon-queren2 green"></span>
<!-- <span class="iconfont icon-shanchu red" ></span> -->
<p class="tit">订单支付成功</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>微信支付</span></p>
<p>支付金额:<span>¥1899.00</span></p>
<div class="btn">
<XtxButton type="primary" style="margin-right:20px">查看订单</XtxButton>
<XtxButton type="gray">进入首页</XtxButton>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
</p>
</div>
</div>
</div>
</template>

<style scoped lang="less">
.pay-result {
padding: 100px 0;
background: #fff;
text-align: center;
> .iconfont {
font-size: 100px;
}
.green {
color: #1dc779;
}
.red {
color: @priceColor;
}
.tit {
font-size: 24px;
}
.tip {
color: #999;
}
p {
line-height: 40px;
font-size: 16px;
}
.btn {
margin-top: 50px;
}
.alert {
font-size: 12px;
color: #999;
margin-top: 50px;
}
}
</style>
  1. 配置路由。
1
2
3
4
5
6
7
8
9
10
11
{
path: '/',
component: Layout,
children: [
...,
{
path: '/pay/callback',
component: () => import('@/views/member/pay/callback.vue')
}
]
},
  1. 根据地址订单 ID 查询订单状态进行展示。
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
<script setup lang="ts">
import { ApiRes } from '@/types/data'
import { OrderResponse } from '@/types/order'
import request from '@/utils/request'
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const payResult = route.query.payResult === 'true'
const order = ref<OrderResponse>({} as OrderResponse)
onMounted(async () => {
// 根据订单id获取订单详情
const res = await request.get<ApiRes<OrderResponse>>(
`/member/order/${route.query.orderId}`
)
order.value = res.data.result
})
</script>

<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付结果</XtxBreadItem>
</XtxBread>
<!-- 支付结果 -->
<div class="pay-result">
<span v-if="payResult" class="iconfont icon-queren2 green"></span>
<span v-else class="iconfont icon-shanchu red"></span>
<p class="tit">{{ payResult ? '订单支付成功' : '订单支付失败' }}</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>支付宝支付</span></p>
<p>
支付金额:<span>{{ order.payMoney }}</span>
</p>
<div class="btn">
<XtxButton type="primary" style="margin-right: 20px">
查看订单
</XtxButton>
<XtxButton type="gray">进入首页</XtxButton>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
</p>
</div>
</div>
</div>
</template>