index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <template>
  2. <view class="page" v-if="payPrice">
  3. <view class="pay-price">
  4. <view class="price">
  5. <text class="unit">¥</text>
  6. <numberScroll :num='(payPrice / 100.0).toFixed(2)' color="#E93323" width='30' height='50' fontSize='50' />
  7. </view>
  8. <view class="count-down">
  9. 支付剩余时间:
  10. <countDown :is-day="false" :tip-text="' '" :day-text="' '" :hour-text="' : '" :minute-text="' : '" :second-text="' '"
  11. :datatime="invalidTime / 1000" :is-col="true" :bgColor="bgColor" />
  12. </view>
  13. </view>
  14. <view class="payment" v-if="channelCode.length > 0">
  15. <view class="title">
  16. 支付方式
  17. </view>
  18. <view class="item acea-row row-between-wrapper" v-for="(item,index) in channels" :key="index" @click="payType(item.code)">
  19. <view class="left acea-row row-between-wrapper">
  20. <view class="iconfont" :class="item.icon"></view>
  21. <view class="text">
  22. <view class=name>{{item.name}}</view>
  23. <view class="info" v-if="item.code === 'wallet'">
  24. {{item.title}} <span class="money">¥{{ fen2yuan(wallet.balance || 0) }}</span>
  25. </view>
  26. <view class="info" v-else>{{item.title}}</view>
  27. </view>
  28. </view>
  29. <view class="iconfont" :class="item.code === channelCode ? 'icon-xuanzhong11 font-num':'icon-weixuan'" />
  30. </view>
  31. </view>
  32. <view class="btn">
  33. <view class="button acea-row row-center-wrapper" @click='goPay(channelCode)'>确认支付</view>
  34. <view class="wait-pay" @click="goReturnUrl('cancel')">暂不支付</view>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. import CountDown from "@/components/countDown";
  40. import numberScroll from '@/components/numberScroll.vue'
  41. import * as PayOrderApi from '@/api/pay/order.js';
  42. import * as PayChannelApi from '@/api/pay/channel.js';
  43. import * as WalletApi from '@/api/pay/wallet.js';
  44. import {fen2yuan} from "../../../utils/util";
  45. import { mapGetters } from "vuex";
  46. export default {
  47. computed: mapGetters([ 'openid' ]),
  48. components: {
  49. CountDown,
  50. numberScroll
  51. },
  52. data() {
  53. return {
  54. orderId: 0, // 支付单号
  55. returnUrl: '', // 调回地址
  56. payPrice: 0, // 支付金额
  57. invalidTime: 0, // 过期时间
  58. wallet: {},
  59. channelCode: '', // 选中的支付渠道
  60. channels: [{ // 支付方式
  61. name: '微信支付', // 微信公众号
  62. icon: "icon-weixin2",
  63. title: '使用微信快捷支付',
  64. code: "wx_pub"
  65. }, {
  66. name: '微信支付', // 微信小程序
  67. icon: "icon-weixin2",
  68. title: '使用微信快捷支付',
  69. code: "wx_lite"
  70. }, {
  71. name: '支付宝支付',
  72. icon: "icon-zhifubao",
  73. title: '使用支付宝支付',
  74. code: "alipay_wap"
  75. }, {
  76. name: '余额支付',
  77. icon: "icon-yuezhifu",
  78. title: '可用余额',
  79. code: "wallet"
  80. }, {
  81. name: '模拟支付',
  82. icon: "icon-yuezhifu",
  83. title: '使用模拟支付',
  84. code: "mock"
  85. },
  86. // 如下是各种示例接入的示例,默认关闭
  87. {
  88. name: '支付宝支付(PC)', // PC 支付
  89. icon: "icon-zhifubao",
  90. title: '使用支付宝支付',
  91. code: "alipay_pc"
  92. }],
  93. bgColor: {
  94. 'bgColor': '#fff',
  95. 'Color': '#E93323',
  96. 'width': '44rpx',
  97. 'timeTxtwidth': '16rpx',
  98. 'isDay': true
  99. },
  100. }
  101. },
  102. onLoad(options) {
  103. if (options.order_id) {
  104. this.orderId = options.order_id
  105. }
  106. if (options.returnUrl) {
  107. this.returnUrl = decodeURIComponent(options.returnUrl)
  108. }
  109. this.getCashierOrder()
  110. },
  111. methods: {
  112. fen2yuan,
  113. getCashierOrder() {
  114. uni.showLoading({
  115. title: '加载订单中'
  116. });
  117. PayOrderApi.getOrder(this.orderId).then(res => {
  118. // 如果已支付、或者已关闭,则直接跳转
  119. if (!res.data){
  120. this.goReturnUrl('close');
  121. return;
  122. }
  123. if (res.data.status === 10) {
  124. this.goReturnUrl('success');
  125. return;
  126. } else if (res.data.status === 20) {
  127. this.goReturnUrl('close');
  128. return;
  129. }
  130. // 设置属性
  131. this.payPrice = res.data.price
  132. this.invalidTime = res.data.expireTime
  133. // 移除多余的支付渠道;
  134. this.removeDisableChannel(res.data.appId);
  135. uni.hideLoading();
  136. }).catch(err => {
  137. uni.hideLoading();
  138. return this.$util.Tips({
  139. title: err
  140. })
  141. })
  142. },
  143. goPay(channelCode) {
  144. if (!this.orderId) {
  145. return this.$util.Tips({
  146. title: '请选择要支付的订单'
  147. });
  148. }
  149. if (channelCode === 'yue' && parseFloat(number) < parseFloat(this.payPrice)) {
  150. return this.$util.Tips({
  151. title: '余额不足'
  152. });
  153. }
  154. uni.showLoading({
  155. title: '支付中'
  156. });
  157. PayOrderApi.submitOrder({
  158. id: this.orderId,
  159. channelCode: channelCode,
  160. returnUrl: this.getPayReturnUrl(),
  161. channelExtras: { // TODO 芋艿:等登录接入完成,需要改成动态读取
  162. // openid: "ockUAwIZ-0OeMZl9ogcZ4ILrGba0" // wx_pub 微信公众号支付的 openid
  163. // openid: "oLefc4g5GjKWHJjLjMSXB3wX0fD0" // wx_lite 微信小程序支付的 openid
  164. openid: this.openid
  165. }
  166. }).then(res => {
  167. this.handleSubmitOrderResult(res.data);
  168. }).catch(err => {
  169. uni.hideLoading();
  170. this.$util.Tips({
  171. title: err
  172. })
  173. })
  174. },
  175. handleSubmitOrderResult(data) {
  176. // 1. 如果已支付、或者已关闭,则直接跳转
  177. if (data.status === 10) {
  178. this.goReturnUrl('success');
  179. return;
  180. } else if (data.status === 20) {
  181. this.goReturnUrl('close');
  182. return;
  183. }
  184. // 2. 根据 displayMode 模式,进行对应的处理
  185. const displayMode = data.displayMode;
  186. const displayContent = data.displayContent
  187. // 2.1 如果返回的是 URL,则直接跳转
  188. if (displayMode === 'url') {
  189. window.location = displayContent;
  190. return;
  191. }
  192. // 2.2 如果返回的是 APP,则自定义处理
  193. if (displayMode === 'app') {
  194. if (this.channelCode === 'wx_pub') {
  195. this.handleSubmitOrderResultForWxPub(displayContent)
  196. return;
  197. }
  198. if (this.channelCode === 'wx_lite') {
  199. this.handleSubmitOrderResultForWxLite(displayContent)
  200. return;
  201. }
  202. }
  203. },
  204. /**
  205. * 发起微信公众号支付
  206. */
  207. handleSubmitOrderResultForWxPub(displayContent) {
  208. const payConfig = JSON.parse(displayContent);
  209. this.$wechat.pay({
  210. timestamp: payConfig.timeStamp,
  211. nonceStr: payConfig.nonceStr,
  212. package: payConfig.packageValue,
  213. signType: payConfig.signType,
  214. paySign: payConfig.paySign,
  215. }).then(res => {
  216. // 失败的情况
  217. if (res.errMsg === 'chooseWXPay:cancel') {
  218. return this.$util.Tips({
  219. title: '取消微信支付'
  220. });
  221. }
  222. if (res.errMsg) {
  223. return this.$util.Tips({
  224. title: res.errMsg,
  225. icon: 'error'
  226. })
  227. }
  228. // 成功的情况
  229. return this.$util.Tips({
  230. title: '支付成功',
  231. icon: 'success'
  232. }, () => {
  233. this.goReturnUrl('success');
  234. });
  235. }).catch(res => {
  236. return this.$util.Tips({
  237. title: '初始化微信支付失败,请重试或者选择其它支付方式',
  238. icon: 'error'
  239. })
  240. })
  241. },
  242. /**
  243. * 发起微信小程序支付
  244. */
  245. handleSubmitOrderResultForWxLite(displayContent) {
  246. const payConfig = JSON.parse(displayContent);
  247. uni.requestPayment({
  248. timeStamp: payConfig.timeStamp,
  249. nonceStr: payConfig.nonceStr,
  250. package: payConfig.packageValue,
  251. signType: payConfig.signType,
  252. paySign: payConfig.paySign,
  253. success: res => {
  254. uni.hideLoading();
  255. return this.$util.Tips({
  256. title: '支付成功',
  257. icon: 'success'
  258. }, () => {
  259. this.goReturnUrl('success');
  260. });
  261. },
  262. fail: e => {
  263. uni.hideLoading();
  264. // 关闭支付的情况
  265. if (e.errMsg === 'requestPayment:cancel'
  266. || e.errMsg === 'requestPayment:fail cancel') {
  267. return this.$util.Tips({
  268. title: '取消支付'
  269. });
  270. }
  271. return this.$util.Tips({
  272. title: e.errMsg,
  273. icon: 'error'
  274. });
  275. }
  276. })
  277. },
  278. /**
  279. * 移除被禁用的支付渠道
  280. */
  281. removeDisableChannel(appId) {
  282. // 1.1 如果不在小程序里,则移除微信小程序支付
  283. // #ifndef MP
  284. this.channels = this.channels.filter(item => item.code !== 'wx_lite')
  285. // #endif
  286. // #ifdef MP
  287. this.channels = this.channels.filter(item => item.code !== 'wx_pub')
  288. // #endif
  289. // 1.2 如果不是公众号环境,则移除微信公众号支付
  290. // #ifdef H5
  291. if (!this.$wechat.isWeixin()) {
  292. this.channels = this.channels.filter(item => item.code !== 'wx_pub')
  293. }
  294. // #endif
  295. // 2. 读取配置,移除被禁用的支付渠道
  296. PayChannelApi.getEnableChannelCodeList(appId).then(res => {
  297. this.channels = this.channels.filter(item => res.data.includes(item.code));
  298. // 默认选中第一个
  299. if (this.channels.length > 0) {
  300. this.payType(this.channels[0].code)
  301. }
  302. })
  303. // 3. // 获得钱包
  304. WalletApi.getPayWallet().then(res=>{
  305. this.wallet = res.data;
  306. })
  307. },
  308. /**
  309. * 设置支付方式
  310. */
  311. payType(channelCode) {
  312. this.channelCode = channelCode
  313. },
  314. /**
  315. * 获得支付的 return url
  316. */
  317. getPayReturnUrl() {
  318. // #ifdef H5
  319. return location.href
  320. // #endif
  321. // #ifdef APP-PLUS
  322. return '/pages/goods/order_details/index?order_id=' + this.orderId + '&returnUrl=' + this.returnUrl;
  323. // #endif
  324. return '';
  325. },
  326. /**
  327. * 回到业务的 URL
  328. *
  329. * @param payResult 支付结果
  330. * ① success:支付成功
  331. * ② cancel:取消支付
  332. * ③ close:支付已关闭
  333. */
  334. goReturnUrl(payResult) {
  335. uni.reLaunch({
  336. url: this.returnUrl.indexOf('?') >= 0
  337. ? this.returnUrl + '&payResult=' + payResult
  338. : this.returnUrl + '?payResult=' + payResult
  339. })
  340. },
  341. }
  342. }
  343. </script>
  344. <style lang="scss" scoped>
  345. .page {
  346. .pay-price {
  347. display: flex;
  348. justify-content: center;
  349. flex-direction: column;
  350. align-items: center;
  351. padding: 50rpx 0 40rpx 0;
  352. .price {
  353. color: #E93323;
  354. margin-bottom: 20rpx;
  355. display: flex;
  356. align-items: flex-end;
  357. .unit {
  358. font-size: 34rpx;
  359. font-weight: 500;
  360. line-height: 41rpx;
  361. }
  362. .num {
  363. font-size: 50rpx;
  364. font-weight: 600;
  365. }
  366. }
  367. .count-down {
  368. display: flex;
  369. align-items: center;
  370. background-color: #fff;
  371. padding: 8rpx 28rpx;
  372. border-radius: 40rpx;
  373. font-size: 24rpx;
  374. color: #E93323;
  375. .time {
  376. margin-top: 0 !important;
  377. }
  378. /deep/.red {
  379. margin: 0 !important;
  380. }
  381. }
  382. }
  383. .payment {
  384. width: 690rpx;
  385. border-radius: 14rpx 14rpx;
  386. background-color: #fff;
  387. z-index: 999;
  388. margin: 0 30rpx;
  389. .title {
  390. color: #666666;
  391. font-size: 26rpx;
  392. padding: 30rpx 0 0 30rpx;
  393. }
  394. .payMoney {
  395. font-size: 28rpx;
  396. color: #333333;
  397. text-align: center;
  398. margin-top: 50rpx;
  399. .font-color {
  400. margin-left: 10rpx;
  401. .money {
  402. font-size: 40rpx;
  403. }
  404. }
  405. }
  406. }
  407. .payment.on {
  408. transform: translate3d(0, 0, 0);
  409. }
  410. .icon-xuanzhong11 {
  411. color: #E93323 !important;
  412. }
  413. .payment .item {
  414. border-bottom: 1rpx solid #eee;
  415. height: 130rpx;
  416. margin-left: 30rpx;
  417. padding-right: 30rpx;
  418. }
  419. .payment .item:last-child {
  420. border-bottom: none;
  421. }
  422. .payment .item .left {
  423. flex: 1;
  424. }
  425. .payment .item .left .text {
  426. flex: 1;
  427. }
  428. .payment .item .left .text .name {
  429. font-size: 30rpx;
  430. color: #333;
  431. }
  432. .payment .item .left .text .info {
  433. font-size: 22rpx;
  434. color: #999;
  435. }
  436. .payment .item .left .text .info .money {
  437. color: #ff9900;
  438. }
  439. .payment .item .left .iconfont {
  440. font-size: 50rpx;
  441. color: #09bb07;
  442. margin-right: 28rpx;
  443. }
  444. .payment .item .left .iconfont.icon-zhifubao {
  445. color: #00aaea;
  446. }
  447. .payment .item .left .iconfont.icon-yuezhifu {
  448. color: #ff9900;
  449. }
  450. .payment .item .left .iconfont.icon-yuezhifu1 {
  451. color: #eb6623;
  452. }
  453. .payment .item .left .iconfont.icon-tonglianzhifu1 {
  454. color: #305fd8;
  455. }
  456. .payment .item .iconfont {
  457. font-size: 40rpx;
  458. color: #ccc;
  459. }
  460. .icon-haoyoudaizhifu {
  461. color: #F34C3E !important;
  462. }
  463. .btn {
  464. position: fixed;
  465. left: 30rpx;
  466. display: flex;
  467. flex-direction: column;
  468. align-items: center;
  469. bottom: 30rpx;
  470. bottom: calc(30rpx + constant(safe-area-inset-bottom)); ///兼容 IOS<11.2/
  471. bottom: calc(30rpx + env(safe-area-inset-bottom)); ///兼容 IOS>11.2/
  472. }
  473. .wait-pay {
  474. color: #aaa;
  475. font-size: 24rpx;
  476. padding-top: 20rpx;
  477. }
  478. .button {
  479. width: 690rpx;
  480. height: 90rpx;
  481. border-radius: 45rpx;
  482. color: #FFFFFF;
  483. background-color: #E93323;
  484. }
  485. }
  486. </style>