B2C-09-结算&支付

608 阅读5分钟

结算&支付

结算-页面布局

image.png
落的代码:

  1. 定义组件基础解构和配置路由src/views/member/pay/checkout.vue
<template>
  <div class="xtx-pay-checkout-page">
    <div class="container">
      <XtxBread>
        <XtxBreadItem to="/">首页</XtxBreadItem>
        <XtxBreadItem to="/cart">购物车</XtxBreadItem>
        <XtxBreadItem >填写订单</XtxBreadItem>
      </XtxBread>
      <div class="wrapper">
        <!-- 收货地址 -->
        <!-- 商品信息 -->
        <!-- 配送时间 -->
        <!-- 支付方式 -->
        <!-- 金额明细 -->
        <!-- 提交订单 -->
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'XtxPayCheckoutPage'
}
</script>
<style scoped lang="less">
.xtx-pay-checkout-page {
  .wrapper {
    background: #fff;
  }
}
</style>
  • 配置路由src/router/index.js
const PayCheckout = () => import('@/views/member/pay/checkout')
      { path: '/cart', component: Cart },
+     { path: '/member/checkout', component: PayCheckout }
  1. 完成页面布局效果
<template>
  <div class="xtx-pay-checkout-page">
    <div class="container">
      <XtxBread>
        <XtxBreadItem to="/">首页</XtxBreadItem>
        <XtxBreadItem to="/cart">购物车</XtxBreadItem>
        <XtxBreadItem >填写订单</XtxBreadItem>
      </XtxBread>
      <div class="wrapper">
        <!-- 收货地址 -->
        <h3 class="box-title">收货地址</h3>
        <div class="box-body">
          <div class="address">
            <div class="text">
              <!-- <div class="none">您需要先添加收货地址才可提交订单。</div> -->
              <ul>
                <li><span>收<i/>货<i/>人:</span>朱超</li>
                <li><span>联系方式:</span>132****2222</li>
                <li><span>收货地址:</span>海南省三亚市解放路108号物质大厦1003室</li>
              </ul>
              <a href="javascript:;">修改地址</a>
            </div>
            <div class="action">
              <XtxButton class="btn">切换地址</XtxButton>
              <XtxButton class="btn">添加地址</XtxButton>
            </div>
          </div>
        </div>
        <!-- 商品信息 -->
        <h3 class="box-title">商品信息</h3>
        <div class="box-body">
          <table class="goods">
            <thead>
              <tr>
                <th width="520">商品信息</th>
                <th width="170">单价</th>
                <th width="170">数量</th>
                <th width="170">小计</th>
                <th width="170">实付</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="i in 4" :key="i">
                <td>
                  <a href="javascript:;" class="info">
                    <img src="https://yanxuan-item.nosdn.127.net/cd9b2550cde8bdf98c9d083d807474ce.png" alt="">
                    <div class="right">
                      <p>轻巧多用锅雪平锅 麦饭石不粘小奶锅煮锅</p>
                      <p>颜色:白色 尺寸:10cm 产地:日本</p>
                    </div>
                  </a>
                </td>
                <td>&yen;100.00</td>
                <td>2</td>
                <td>&yen;200.00</td>
                <td>&yen;200.00</td>
              </tr>
            </tbody>
          </table>
        </div>
        <!-- 配送时间 -->
        <h3 class="box-title">配送时间</h3>
        <div class="box-body">
          <a class="my-btn active" href="javascript:;">不限送货时间:周一至周日</a>
          <a class="my-btn" href="javascript:;">工作日送货:周一至周五</a>
          <a class="my-btn" href="javascript:;">双休日、假日送货:周六至周日</a>
        </div>
        <!-- 支付方式 -->
         <h3 class="box-title">支付方式</h3>
        <div class="box-body">
          <a class="my-btn active" href="javascript:;">在线支付</a>
          <a class="my-btn" href="javascript:;">货到付款</a>
          <span style="color:#999">货到付款需付5元手续费</span>
        </div>
        <!-- 金额明细 -->
        <h3 class="box-title">金额明细</h3>
        <div class="box-body">
          <div class="total">
            <dl><dt>商品件数:</dt><dd>5件</dd></dl>
            <dl><dt>商品总价:</dt><dd>¥5697.00</dd></dl>
            <dl><dt>运<i></i>费:</dt><dd>¥0.00</dd></dl>
            <dl><dt>应付总额:</dt><dd class="price">¥5697.00</dd></dl>
          </div>
        </div>
        <!-- 提交订单 -->
        <div class="submit">
          <XtxButton type="primary">提交订单</XtxButton>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'XtxPayCheckoutPage'
}
</script>
<style scoped lang="less">
.xtx-pay-checkout-page {
  .wrapper {
    background: #fff;
    padding: 0 20px;
    .box-title {
      font-size: 16px;
      font-weight: normal;
      padding-left: 10px;
      line-height: 70px;
      border-bottom: 1px solid #f5f5f5;
    }
    .box-body {
      padding: 20px 0;
    }
  }
}
.address {
  border: 1px solid #f5f5f5;
  display: flex;
  align-items: center;
  .text {
    flex: 1;
    min-height: 90px;
    display: flex;
    align-items: center;
    .none {
      line-height: 90px;
      color: #999;
      text-align: center;
      width: 100%;  
    }
    > ul {
      flex: 1;
      padding: 20px;
      li {
        line-height: 30px;
        span {
          color: #999;
          margin-right: 5px;
          > i {
            width: 0.5em;
            display: inline-block;
          }
        }
      }
    }
    > a {
      color: @xtxColor;
      width: 160px;
      text-align: center;
      height: 90px;
      line-height: 90px;
      border-right: 1px solid #f5f5f5;
    }
  }
  .action {
    width: 420px;
    text-align: center;
    .btn {
      width: 140px;
      height: 46px;
      line-height: 44px;
      font-size: 14px;
      &:first-child {
        margin-right: 10px;
      }
    }
  }
}
.goods {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  .info {
    display: flex;
    text-align: left;
    img {
      width: 70px;
      height: 70px;
      margin-right: 20px;
    }
    .right {
      line-height: 24px;
      p {
        &:last-child {
          color: #999;
        }
      }
    }
  }
  tr {
    th {
      background: #f5f5f5;
      font-weight: normal;
    }
    td,th {
      text-align: center;
      padding: 20px;
      border-bottom: 1px solid #f5f5f5;
      &:first-child {
        border-left: 1px solid #f5f5f5;
      }
      &:last-child {
        border-right: 1px solid #f5f5f5;
      }
    }
  }
}
.my-btn {
  width: 228px;
  height: 50px;
  border: 1px solid #e4e4e4;
  text-align: center;
  line-height: 48px;
  margin-right: 25px;
  color: #666666;
  display: inline-block;
  &.active,&:hover {
    border-color: @xtxColor;
  }
}
.total {
  dl {
    display: flex;
    justify-content: flex-end;
    line-height: 50px;
    dt {
      i {
        display: inline-block;
        width: 2em;
      }
    }
    dd {
      width: 240px;
      text-align: right;
      padding-right: 70px;
      &.price {
        font-size: 20px;
        color: @priceColor;
      }
    }
  }
}
.submit {
  text-align: right;
  padding: 60px;
  border-top: 1px solid #f5f5f5;
}
</style>

结算-渲染页面

目的:分离收货地址组件,渲染页面默认内容。



大致步骤:
  • 分离收货地址组件
  • 定义获结算信息API接口
  • 页面组件获取数据,传入地址组件,渲染剩余内容
  • 渲染地址组件


落的代码:
image.png

  1. 分离收货地址组件src/member/pay/components/checkout-address.vue
<template>
  <div class="checkout-address">
    <div class="text">
      <!-- <div class="none">您需要先添加收货地址才可提交订单。</div> -->
      <ul>
        <li><span>收<i/>货<i/>人:</span>朱超</li>
        <li><span>联系方式:</span>132****2222</li>
        <li><span>收货地址:</span>海南省三亚市解放路108号物质大厦1003室</li>
      </ul>
      <a href="javascript:;">修改地址</a>
    </div>
    <div class="action">
      <XtxButton class="btn">切换地址</XtxButton>
      <XtxButton class="btn">添加地址</XtxButton>
    </div>
  </div>
</template>
<script>
export default {
  name: 'CheckoutAddress'
}
</script>
<style scoped lang="less">
.checkout-address {
  border: 1px solid #f5f5f5;
  display: flex;
  align-items: center;
  .text {
    flex: 1;
    min-height: 90px;
    display: flex;
    align-items: center;
    .none {
      line-height: 90px;
      color: #999;
      text-align: center;
      width: 100%;
    }
    > ul {
      flex: 1;
      padding: 20px;
      li {
        line-height: 30px;
        span {
          color: #999;
          margin-right: 5px;
          > i {
            width: 0.5em;
            display: inline-block;
          }
        }
      }
    }
    > a {
      color: @xtxColor;
      width: 160px;
      text-align: center;
      height: 90px;
      line-height: 90px;
      border-right: 1px solid #f5f5f5;
    }
  }
  .action {
    width: 420px;
    text-align: center;
    .btn {
      width: 140px;
      height: 46px;
      line-height: 44px;
      font-size: 14px;
      &:first-child {
        margin-right: 10px;
      }
    }
  }
}
</style>
  • 使用组件src/views/member/pay/checkout.vue
  • image.png
+import CheckoutAddress from './components/checkout-address'
export default {
  name: 'XtxPayCheckoutPage',
+  components: { CheckoutAddress }
}
<!-- 收货地址 -->
<h3 class="box-title">收货地址</h3>
<div class="box-body">
+  <CheckoutAddress />
</div>
  1. 定义获结算信息API接口src/api/order.js   定义接口
import request from '@/utils/request'
/**
 * 获取结算信息
 */
export const findCheckoutInfo = () => {
  return request({
    method: 'get',
    url: '/member/order/pre'
  })
}
  1. 页面组件获取数据,传入地址组件,渲染剩余内容 src/views/member/pay/checkout.vue
+import { findCheckoutInfo } from '@/api/order'
export default {
  name: 'XtxPayCheckoutPage',
  components: { CheckoutAddress },
  setup () {
+    const checkoutInfo = ref(null)
+    findCheckoutInfo().then(data => {
+      checkoutInfo.value = data.result
+    })
+    return { checkoutInfo }
  }
}
  • 传入地址组件,地址列表src/views/member/pay/checkout.vue
+ <div class="wrapper" v-if="checkoutInfo">
    <!-- 收货地址 -->
    <h3 class="box-title">收货地址</h3>
    <div class="box-body">
+   <CheckoutAddress :list="checkoutInfo.userAddresses" />
</div>
  • 渲染剩余内容 src/views/member/pay/checkout.vue
<tbody>
  <tr v-for="item in checkoutInfo.goods" :key="item.id">
    <td>
      <a href="javascript:;" class="info">
        <img :src="item.picture" alt="">
        <div class="right">
          <p>{{item.name}}</p>
          <p>{{item.attrsText}}</p>
        </div>
      </a>
    </td>
    <td>&yen;{{item.payPrice}}</td>
    <td>{{item.count}}</td>
    <td>&yen;{{item.totalPrice}}</td>
    <td>&yen;{{item.totalPayPrice}}</td>
  </tr>
</tbody>
<div class="total">
  <dl><dt>商品件数:</dt><dd>{{checkoutInfo.summary.goodsCount}}件</dd></dl>
  <dl><dt>商品总价:</dt><dd>¥{{checkoutInfo.summary.totalPrice}}</dd></dl>
  <dl><dt>运<i></i>费:</dt><dd>¥{{checkoutInfo.summary.postFee}}</dd></dl>
  <dl><dt>应付总额:</dt><dd class="price">¥{{checkoutInfo.summary.totalPayPrice}}</dd></dl>
</div>
  1. 渲染地址组件src/member/pay/components/checkout-address.vue
  • 接收数据
props: {
  list: {
    type: Array,
    default: () => []
  }
},
  • 得到默认显示地址
setup (props) {
  // 显示的地址
  const showAddress = ref(null)
  if (props.list.length) {
    const defaultAddress = props.list.find(item => item.isDefault === 1)
    if (defaultAddress) {
      showAddress.value = defaultAddress
    } else {
      // eslint-disable-next-line vue/no-setup-props-destructure
      showAddress.value = props.list[0]
    }
  }
  return { showAddress }
}
  • 渲染组件
<div class="text">
  <div v-if="!showAddress" class="none">您需要先添加收货地址才可提交订单。</div>
  <ul v-if="showAddress">
    <li><span>收<i/>货<i/>人:</span>{{showAddress.receiver}}</li>
    <li><span>联系方式:</span>{{showAddress.contact}}</li>
    <li><span>收货地址:</span>{{showAddress.fullLocation.replace(/ /g,'')+showAddress.address}}</li>
  </ul>
  <a v-if="showAddress" href="javascript:;">修改地址</a>
</div>

结算-提交订单

目的:汇总提交订单需要的数据,进行提交。


大致步骤:

  • 定义需要提交的数据对象
  • 绑定提交订单点击事件,进行提交即可


落的代码:

  1. 定义需要提交的数据对象src/views/member/pay/checkout.vue
  setup () {
    const checkoutInfo = ref(null)
    // 需要提交的字段
    const requestParams = reactive({
      addressId: null,
+      deliveryTimeType: 1,
+      payType: 1,
+      buyerMessage: '',
+      goods: []
    })
    findCheckoutInfo().then(data => {
      checkoutInfo.value = data.result
+      // 设置提交时候的商品
+      requestParams.goods = checkoutInfo.value.goods.map(item => {
+        return {
+          skuId: item.skuId,
+          count: item.count
+        }
+      })
    })
  1. 绑定提交订单点击事件,进行提交即可src/api/order.js  提交订单API函数
/**
 * 提交订单
 * @param {Object} order - 订单信息对象
 */
export const createOrder = (order) => {
  return request({
    method: 'post',
    url: '/member/order',
    data: order
  })
}
  • 提交订单src/views/member/pay/checkout.vue
<!-- 提交订单 -->
<div class="submit">
  <XtxButton @click="submitOrder" type="primary">提交订单</XtxButton>
</div>
// 提交订单
const router = useRouter()
const submitOrder = () => {
  if (!requestParams.addressId) return Message({ text: '请选择收货地址' })
  createOrder(requestParams).then(data => {
    router.push({ path: '/member/pay', query: { id: data.result.id } })
  })
}
return { checkoutInfo, changeAddress, submitOrder }

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

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


image.png

  • 支付页面基本布局src/views/member/pay/index.vue
<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>
<script>
export default {
  name: 'XtxPayPage'
}
</script>
<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>
  • 路由配置src/router/index.js
const PayIndex = () => import('@/views/member/pay/index')
{ path: '/member/checkout', component: PayCheckout },
+ { path: '/member/pay', component: PayIndex }

支付-支付页面-信息展示

目的:展示支付的订单相关信息。


大致步骤:

  • 准备API接口函数获取订单详情
  • 在组件获取数据渲染


落的代码:

  1. 准备API接口函数获取订单详情 src/api/order.js
/**
 * 获取订单详情
 * @param {String} id - 订单ID
 */
export const findOrder = (id) => {
  return request({
    method: 'get',
    url: '/member/order/' + id
  })
}
  1. 在组件获取数据渲染  src/views/component/pay/index.vue
import { ref } from 'vue'
import { findOrder } from '@/api/order'
import { useRoute } from 'vue-router'
export default {
  name: 'XtxPayPage',
  setup () {
    // 订单
    const order = ref(null)
    // 路由信息
    const route = useRoute()
    // 查询订单
    findOrder(route.query.id).then(data => {
      // 设置订单
      order.value = data.result
    })
    return { order }
  }
}
+  <div class="pay-info" v-if="order">
<div class="amount">
<span>应付总额:</span>
+   <span>¥{{order.payMoney}}</span>
</div>

支付-支付流程

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


image.png

总结:

  • PC前台点击支付按钮,新开标签页打开后台提供的支付链接带上订单ID还有回跳地址
  • 后台服务发起支付,等待支付结果,修改订单状态,回跳PC前台结果页
  • PC前台在结果页获取回跳URL参数订单ID查询支付状态,展示支付结果
# 支付地址回调地址(可变)
http://www.corho.com:8080/#/pay/callback


测试:如果使用客户端需要下载 沙箱支付宝 开放平台扫码下载。

买家账号jfjbwb4477@sandbox.com
登录密码111111
支付密码111111

支付-跳转支付

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

  1. 准备支付跳转链接src/utils/request.js
export const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net/'
  • 拼接回跳地址链接src/views/member/pay/index.vue
// 支付地址
// const payUrl = '后台服务基准地址+支付页面地址+订单ID+回跳地址'
const redirect = encodeURIComponent('http://www.corho.com:8080/#/pay/callback')
const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.orderId}&redirect=${redirect}`

return { order, countdownText, payUrl }
<a class="btn alipay" :href="payUrl" target="_blank"></a>2.

支付-结果展示

目的:准备一个支付完成的回调页面,展示支付后订单状态。


image.png

大致步骤:

  • 准备一个基础页面
  • 根据地址订单ID查询订单状态进行展示,或者是地址栏支付结果。


落的代码:

  1. 准备一个基础页面
<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>
<script>
export default {
  name: 'XtxPayResultPage'
}
</script>
<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. 根据地址订单ID查询订单状态进行展示
<template>
  <div class="xtx-pay-page">
    <div class="container">
      <XtxBread>
        <XtxBreadItem to="/">首页</XtxBreadItem>
        <XtxBreadItem to="/cart">购物车</XtxBreadItem>
        <XtxBreadItem>支付结果</XtxBreadItem>
      </XtxBread>
      <!-- 支付结果 -->
      <div class="pay-result" v-if="order">
        <span v-if="$route.query.payResult" class="iconfont icon-queren2 green"></span>
        <span v-else class="iconfont icon-shanchu red" ></span>
        <p class="tit">订单支付{{$route.query.payResult?'成功':'失败'}}</p>
        <p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
        <p>支付方式:<span>支付宝支付</span></p>
        <p>支付金额:<span class="red">¥{{order.payMoney}}</span></p>
        <div class="btn">
          <XtxButton @click="$router.push('/member/order')" type="primary" style="margin-right:20px">查看订单</XtxButton>
          <XtxButton @click="$router.push('/')" type="gray">进入首页</XtxButton>
        </div>
        <p class="alert">
          <span class="iconfont icon-tip"></span>
          温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
        </p>
      </div>
    </div>
  </div>
</template>
<script>
import { ref } from 'vue'
import { findOrderDetail } from '@/api/order'
import { useRoute } from 'vue-router'
export default {
  name: 'XtxPayResultPage',
  setup () {
    const order = ref(null)
    const route = useRoute()
    findOrderDetail(route.query.orderId).then(data => {
      order.value = data.result
    })
    return { order }
  }
}
</script>
<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>