order-detail.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. import OHeader from '@/components/m-header';
  2. import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
  3. import styles from './order-detail.module.less';
  4. import Addres from './component/addres';
  5. import OSticky from '@/components/m-sticky';
  6. import { Button, Cell, CellGroup, Image, Popup, showToast, Tag } from 'vant';
  7. import Payment from '@/views/adapay/payment';
  8. import { useRoute, useRouter } from 'vue-router';
  9. import request from '@/helpers/request';
  10. import { state as baseState } from '@/state';
  11. import { browser, moneyFormat } from '@/helpers/utils';
  12. import OProtocol from '@/components/m-protocol';
  13. import OPopup from '@/components/m-popup';
  14. import UserAuth from './component/user-auth';
  15. import qs from 'query-string';
  16. import ODialog from '@/components/m-dialog';
  17. import { orderStatus } from '@/helpers/constant';
  18. import QrcodePayment from './qrcode-payment';
  19. import { beforeSubmit } from './order-state';
  20. /**
  21. * 接入jsdk
  22. * 乐团报名-原生js支付
  23. * 会员购买-汇付
  24. */
  25. export default defineComponent({
  26. name: 'order-detail',
  27. setup() {
  28. const route = useRoute();
  29. const router = useRouter();
  30. const state = reactive({
  31. paymentType: 'adapay' as 'wxpay' | 'adapay', // 支付方式
  32. orderTimer: null as any,
  33. paymentStatus: false,
  34. showQrcode: false,
  35. qrCodeUrl: '',
  36. pay_channel: '',
  37. orderNo: route.query.orderNo,
  38. orderInfo: {} as any, // 订单信息
  39. goodsInfos: [] as any, // 订单信息列表
  40. config: route.query.config ? JSON.parse(route.query.config as any) : {},
  41. hasFreight: route.query.hf ? false : true, // 是否显示
  42. freight: '', // 运费
  43. agreeStatus: true, //是否勾选协议
  44. showHeader: false,
  45. authShow: false, // 是否进行实名认证
  46. selectGoodsId: null as any,
  47. currentPrice: 0,
  48. hasInstrument: false, // 是否有乐器
  49. dialogStatus: false,
  50. dialogMessage: '',
  51. submitStatus: false
  52. });
  53. const orderType = computed(() => {
  54. return state.orderInfo.orderType;
  55. });
  56. const addressDetails = ref<any>({});
  57. const getOrderDetails = async () => {
  58. try {
  59. const { data } = await request.get(
  60. '/edu-app/userPaymentOrder/detail/' + state.orderNo
  61. );
  62. const goodsInfos = data.goodsInfos || [];
  63. state.orderInfo = data;
  64. let hasInstrument = false; // 是否有乐器
  65. goodsInfos.forEach((item: any) => {
  66. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : '';
  67. item.goodsUrl = img;
  68. if (item.goodsType === 'INSTRUMENTS') {
  69. hasInstrument = true;
  70. }
  71. });
  72. state.goodsInfos = goodsInfos;
  73. if (!addressDetails.value.id) {
  74. addressDetails.value = data.addresses || {};
  75. }
  76. // 判断运费状态
  77. // 如果没有购买商品,有购买教材则『到付』 其它则免运费
  78. state.hasInstrument = hasInstrument;
  79. if (hasInstrument) {
  80. state.freight = '到付';
  81. } else {
  82. state.freight = '免运费';
  83. }
  84. // 判断订单状态,如果不是待支付则返回
  85. // WAIT_PAY: '待支付',
  86. // PAYING: '支付中',
  87. // PAID: '已付款',
  88. // TIMEOUT: '订单超时',
  89. // FAIL: '支付失败',
  90. // CLOSED: '订单关闭',
  91. // REFUNDING: '退款中',
  92. // REFUNDED: '已退款'
  93. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  94. // status
  95. state.dialogStatus = true;
  96. state.dialogMessage = '订单' + orderStatus[data.status];
  97. }
  98. } catch {
  99. //
  100. }
  101. };
  102. const onConfirm = (val: any) => {
  103. const config: any = state.config;
  104. state.pay_channel = val.pay_channel;
  105. const params = qs.stringify({
  106. pay_channel: val.pay_channel,
  107. wxAppId: config.wxAppId,
  108. alipayAppId: config.alipayAppId,
  109. paymentType: state.paymentType,
  110. body: config.body,
  111. price: config.price,
  112. orderNo: config.merOrderNo,
  113. userId: config.userId
  114. });
  115. if (val.payCode === 'payResult') {
  116. window.location.href =
  117. window.location.origin + '/classroom-app/#/payResult?' + params;
  118. } else {
  119. state.qrCodeUrl =
  120. window.location.origin + '/classroom-app/#/payDefine?' + params;
  121. state.showQrcode = true;
  122. state.paymentStatus = false;
  123. setTimeout(() => {
  124. getPaymentOrderStatus();
  125. }, 300);
  126. }
  127. };
  128. // 轮询查询订单状态
  129. const getPaymentOrderStatus = async () => {
  130. // 循环查询订单
  131. // const orderNo = state.orderNo
  132. const orderTimer = setInterval(async () => {
  133. // 判断是否在当前路由,如果不是则清除定时器
  134. if (route.name != 'order-detail') {
  135. clearInterval(orderTimer);
  136. return;
  137. }
  138. state.orderTimer = orderTimer;
  139. try {
  140. const { data } = await request.post(
  141. '/edu-app/open/userOrder/paymentStatus/' + state.orderNo,
  142. {
  143. hideLoading: true
  144. }
  145. );
  146. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  147. // 默认关闭支付二维码弹窗
  148. state.showQrcode = false;
  149. clearInterval(state.orderTimer);
  150. setTimeout(() => {
  151. checkOrderTypeJump();
  152. }, 100);
  153. }
  154. } catch {
  155. //
  156. clearInterval(state.orderTimer);
  157. }
  158. }, 5000);
  159. };
  160. // 确定支付
  161. const onSubmit = async () => {
  162. clearInterval(state.orderTimer);
  163. if (orderType.value === 'VIP') {
  164. buyVip(onCallback);
  165. } else {
  166. buyOrchestra(onCallback);
  167. }
  168. };
  169. /**
  170. * @description 回调,判断是否有支付渠道,如果有则直接去支付
  171. * @returns void
  172. */
  173. const onCallback = () => {
  174. const pt = state.pay_channel;
  175. // 判断是否有支付方式
  176. if (pt) {
  177. const payCode: string = beforeSubmit(state.pay_channel);
  178. onConfirm({
  179. payCode,
  180. pay_channel: pt
  181. });
  182. } else {
  183. if (orderType.value === 'VIP') {
  184. state.paymentStatus = true;
  185. } else {
  186. // 直接去拉取微信支付
  187. onConfirm({
  188. payCode: 'payResult',
  189. pay_channel: 'wx_pub'
  190. });
  191. }
  192. }
  193. };
  194. /**
  195. * @description 会员购买
  196. * @param callback 回调方式
  197. */
  198. const buyVip = async (callback?: any) => {
  199. try {
  200. if (!state.agreeStatus) {
  201. showToast('请先阅读并同意《音乐数字课堂服务协议》');
  202. return;
  203. }
  204. const users = baseState.user.data;
  205. // 判断是否需要实名认证, 姓名,卡号
  206. if (!users?.account.realName || !users?.account.idCardNo) {
  207. state.authShow = true;
  208. return;
  209. }
  210. state.submitStatus = true;
  211. // const { data } = await request.get(
  212. // '/edu-app/userPaymentOrder/detail/' + state.orderNo,
  213. // {
  214. // hideLoading: false
  215. // }
  216. // );
  217. const { data } = await request.post(
  218. '/edu-app/userPaymentOrder/updateReceiveAddress',
  219. {
  220. hideLoading: false,
  221. data: {
  222. orderNo: state.orderNo,
  223. orderType: 'VIP'
  224. }
  225. }
  226. );
  227. state.pay_channel = data.paymentChannel;
  228. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  229. router.replace({
  230. path: '/payment-result',
  231. query: {
  232. orderNo: state.orderNo
  233. }
  234. });
  235. } else {
  236. callback && callback();
  237. state.submitStatus = false;
  238. }
  239. } catch {
  240. //
  241. state.submitStatus = false;
  242. }
  243. };
  244. /**
  245. * @description 学生登记 - 汇付
  246. * @param callback 回调方式
  247. */
  248. const buyOrchestra = async (callback: any) => {
  249. // 请选择收货地址
  250. if (!addressDetails.value.id && state.hasInstrument) {
  251. showToast('请选择收货地址');
  252. return;
  253. }
  254. if (!state.agreeStatus) {
  255. showToast('请先阅读并同意《音乐数字课堂服务协议》');
  256. return;
  257. }
  258. const users = baseState.user.data;
  259. // 判断是否需要实名认证, 姓名,卡号
  260. if (!users?.account.realName || !users?.account.idCardNo) {
  261. state.authShow = true;
  262. return;
  263. }
  264. state.submitStatus = true;
  265. try {
  266. const { data } = await request.post(
  267. '/edu-app/userPaymentOrder/updateReceiveAddress',
  268. {
  269. hideLoading: false,
  270. data: {
  271. orderNo: state.orderNo,
  272. orderType: 'SCHOOL_REGISTER',
  273. receiveAddress: addressDetails.value.id || ''
  274. }
  275. }
  276. );
  277. state.pay_channel = data.paymentChannel;
  278. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  279. checkOrderTypeJump();
  280. } else {
  281. callback && callback();
  282. state.submitStatus = false;
  283. }
  284. } catch {
  285. //
  286. state.submitStatus = false;
  287. }
  288. };
  289. // 有支付结果后回调
  290. const checkOrderTypeJump = () => {
  291. // 判断是否是乐团报名
  292. // if (orderType.value === 'SCHOOL_REGISTER') {
  293. // window.location.replace(
  294. // 'https://mp.weixin.qq.com/s?__biz=MzkxMDMwOTI5Nw==&mid=2247485362&idx=3&sn=9b265d36b5dabe7f9393fc679c367540&chksm=c12c256cf65bac7ae2a865435b950f6e1285afd226356db0ffde815b1ee345f29cfcdb798cc9#rd'
  295. // );
  296. // } else {
  297. router.replace({
  298. path: '/payment-result',
  299. query: {
  300. orderNo: state.orderNo
  301. }
  302. });
  303. // }
  304. };
  305. // 放弃支付时,则取消订单
  306. const onBackOut = async () => {
  307. try {
  308. await request.post(
  309. '/edu-app/userPaymentOrder/cancelPayment/' + state.orderNo
  310. );
  311. router.back();
  312. } catch {
  313. //
  314. }
  315. };
  316. // 实名认证成功
  317. const onAuthSuccess = () => {
  318. //
  319. state.authShow = false;
  320. onSubmit(); // 实名成功后自动支付
  321. };
  322. onMounted(() => {
  323. if (browser().isApp) {
  324. state.showHeader = true;
  325. } else {
  326. state.showHeader = false;
  327. }
  328. // 获取收货地址
  329. let details = sessionStorage.getItem('addressDetails');
  330. details = details ? JSON.parse(details) : {};
  331. addressDetails.value = details;
  332. sessionStorage.removeItem('addressDetails');
  333. getOrderDetails();
  334. });
  335. return () => (
  336. <>
  337. {browser().isApp && <OHeader border={false} />}
  338. <div class={styles.cartConfirm}>
  339. {/* 只有乐团购买的时候显示收货地址 */}
  340. {/* {orderType.value === 'SCHOOL_REGISTER' && } */}
  341. {state.hasInstrument && (
  342. <div class={styles.cartConfirmBox}>
  343. <Addres item={addressDetails.value} />
  344. </div>
  345. )}
  346. <CellGroup style={{ margin: 0 }}>
  347. {state.goodsInfos &&
  348. state.goodsInfos.map((goods: any) => (
  349. <Cell class={styles.cellItem}>
  350. {{
  351. icon: () => (
  352. <Image class={styles.img} src={goods.goodsUrl} />
  353. ),
  354. title: () => (
  355. <div class={styles.goodsContent}>
  356. <h2>
  357. <span>{goods.goodsName}</span>
  358. <span class={styles.goodsNum}>
  359. x {goods.goodsNum}
  360. </span>
  361. </h2>
  362. <div class={styles.goodsPrice}>
  363. <Tag class={styles.brandName}>
  364. {goods.goodsType === 'VIP'
  365. ? '12个月'
  366. : goods.brandName}
  367. </Tag>
  368. <span
  369. class={[
  370. styles.goodsNums,
  371. goods.paymentCashAmount > 0
  372. ? styles.numFont
  373. : styles.free
  374. ]}>
  375. {goods.paymentCashAmount > 0 ? (
  376. <>
  377. <span class={styles.numPrefix}>¥ </span>
  378. {moneyFormat(goods.paymentCashAmount)}
  379. </>
  380. ) : (
  381. '免费'
  382. )}
  383. </span>
  384. </div>
  385. <p class={styles.model}>{goods.description}</p>
  386. </div>
  387. )
  388. }}
  389. </Cell>
  390. ))}
  391. </CellGroup>
  392. {orderType.value === 'SCHOOL_REGISTER' && (
  393. <Cell
  394. class={styles.freight}
  395. title="运费"
  396. value={state.freight}></Cell>
  397. )}
  398. </div>
  399. <OSticky position="bottom">
  400. {/* <div class={styles.protocol}>
  401. <OProtocol
  402. v-model:modelValue={state.agreeStatus}
  403. showHeader={state.showHeader}
  404. style={{ paddingTop: 0, paddingBottom: 0 }}
  405. />
  406. </div> */}
  407. <div class={styles.paymentContainer}>
  408. <div class={styles.payemntPrice}>
  409. <p class={styles.needPrice}>
  410. 支付金额:
  411. <span>
  412. <i>¥</i>
  413. {moneyFormat(state.orderInfo.currentPrice)}
  414. </span>
  415. </p>
  416. </div>
  417. <div class={styles.paymentBtn}>
  418. <Button
  419. round
  420. class={[
  421. styles.submitBtn
  422. // orderType.value === 'VIP' ? styles.vipBtn : '',
  423. // orderType.value === 'SCHOOL_REGISTER' ? styles.otherBtn : ''
  424. ]}
  425. onClick={onSubmit}
  426. loading={state.submitStatus}
  427. disabled={state.submitStatus}>
  428. 提交
  429. </Button>
  430. </div>
  431. </div>
  432. </OSticky>
  433. <Popup
  434. show={state.paymentStatus}
  435. closeOnClickOverlay={false}
  436. position="bottom"
  437. round
  438. closeOnPopstate
  439. safeAreaInsetBottom
  440. style={{ minHeight: '30%' }}>
  441. <Payment
  442. paymentConfig={state.orderInfo}
  443. onClose={() => (state.paymentStatus = false)}
  444. onBackOut={onBackOut}
  445. onConfirm={(val: any) => onConfirm(val)}
  446. />
  447. </Popup>
  448. <Popup
  449. v-model:show={state.showQrcode}
  450. round
  451. onClose={() => {
  452. // 二维码关闭时清除定时器
  453. clearInterval(state.orderTimer);
  454. }}>
  455. <QrcodePayment
  456. url={state.qrCodeUrl}
  457. pay_channel={state.pay_channel}
  458. orderType={orderType.value}
  459. />
  460. </Popup>
  461. <OPopup v-model:modelValue={state.authShow}>
  462. <UserAuth onSuccess={onAuthSuccess} hideHeader={!browser().isApp} />
  463. </OPopup>
  464. <ODialog
  465. title="提示"
  466. v-model:show={state.dialogStatus}
  467. message={state.dialogMessage}
  468. confirmButtonText="确定"
  469. onConfirm={() => {
  470. checkOrderTypeJump();
  471. }}
  472. />
  473. </>
  474. );
  475. }
  476. });