usePayment.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import {ref, onMounted, onUnmounted} from 'vue'
  2. import {createOrder as createOrderApi, getCardPrice, queryOrder} from "@/api/system/user";
  3. import {createEventHook, useIntervalFn, useLocalStorage, useTimeout} from "@vueuse/core";
  4. import mxConst from "@/common/mxConst";
  5. import _ from "lodash";
  6. import {func, number} from "@/uni_modules/uv-ui-tools/libs/function/test";
  7. import {useTransfer} from "@/hooks/useTransfer";
  8. import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
  9. import config from "@/config";
  10. import {useEnvStore} from "@/hooks/useEnvStore";
  11. import {useUserStore} from "@/hooks/useUserStore";
  12. export const paymentState = {
  13. NOTPAY: 'NOTPAY',
  14. SUCCESS: 'SUCCESS',
  15. CLOSED: 'CLOSED'
  16. }
  17. export const defaultPaymentOptions = {
  18. onSucceed: () => {
  19. const {transferTo} = useTransfer()
  20. uni.hideLoading()
  21. uni.showModal({
  22. content: '支付成功, 请查收短信并绑定VIP卡',
  23. confirmText: '去绑卡',
  24. success: res => {
  25. if (res.confirm) {
  26. transferTo(mxConst.routes.activate)
  27. }
  28. }
  29. })
  30. },
  31. onTimeout: (msg) => {
  32. uni.hideLoading()
  33. uni.showModal({
  34. content: msg,
  35. showCancel: false
  36. })
  37. },
  38. onFailed: () => {
  39. uni.hideLoading()
  40. // only print error info on console.
  41. console.error(...arguments)
  42. }
  43. }
  44. export const usePayment = function (options = defaultPaymentOptions) {
  45. const orderSucceed = createEventHook()
  46. const orderFailed = createEventHook()
  47. const orderTimeout = createEventHook()
  48. const price = ref(798)
  49. const outTradeNo = useLocalStorage(mxConst.keyOutTradeNo, '')
  50. const interval = ref(1500)
  51. let queryTimes = 8;
  52. let iframe = null
  53. let lock = false
  54. onMounted(() => loadPrice())
  55. onUnmounted(() => {
  56. // clear resources
  57. outTradeNo.value = null
  58. clearResultFrame()
  59. })
  60. const payment = async function () {
  61. if (lock) return toast('支付中,请稍候...')
  62. uni.showLoading({title:"", mask: true})
  63. try {
  64. lock = true // will release while trigger func be called
  65. const {h5url, outTradeNo: no} = await createOrder()
  66. if (h5url && no) {
  67. outTradeNo.value = no
  68. clearResultFrame()
  69. queryTimes = 8;
  70. iframe = await createResultFrame(h5url)
  71. } else {
  72. triggerFailed('Payment: create order failed', h5url, no)
  73. }
  74. } catch (e) {
  75. triggerFailed('Payment: exception', e)
  76. }
  77. }
  78. const iosPayment = async function () {
  79. const {isWap2App} = useEnvStore();
  80. const {token} = useUserStore();
  81. if (isWap2App.value && window.webviewBridge) {
  82. uni.showLoading({
  83. title: "",
  84. });
  85. const { orderId } = await createOrder('ios');
  86. uni.hideLoading();
  87. if (orderId) {
  88. let eventArgs = []
  89. window.webviewBridge.applePay({
  90. orderId,
  91. token: token.value,
  92. baseUrl: config.serverBaseUrl + '/front/ecard/iosVerifyResult'
  93. }).then(res => {
  94. if (res === "支付成功") {
  95. eventArgs = ['交易成功', orderId]
  96. triggerSucceed(...eventArgs)
  97. } else {
  98. eventArgs = ['支付失败', orderId]
  99. triggerFailed(...eventArgs)
  100. }
  101. });
  102. }
  103. } else {
  104. toast("请在app中操作");
  105. }
  106. }
  107. const loadPrice = async function () {
  108. const res = await getCardPrice()
  109. const val = _.get(res, 'data[0].price')
  110. if (number(val)) price.value = val / 100
  111. }
  112. const createOrder = async function (type) {
  113. const {data: {h5url, outTradeNo, orderId}} = await createOrderApi({totalFee: price.value, type})
  114. return {h5url, outTradeNo, orderId}
  115. }
  116. const createResultFrame = async function (h5url) {
  117. const { isH5, isWap2App } = useEnvStore();
  118. if (isH5.value) {
  119. await useTimeout()
  120. const iframe = document.createElement('iframe');
  121. const style = `width:0px;height:0px;position: absolute;opacity: 0;z-index: -1;outline:none;border:none;`;
  122. iframe.setAttribute('style', style);
  123. iframe.setAttribute('sandbox', 'allow-scripts allow-top-navigation allow-same-origin');
  124. iframe.style.position = 'absolute';
  125. iframe.src = h5url;
  126. iframe.onload = () => checkOrderStatus()
  127. iframe.onerror = () => console.error('Payment: iframe加载失败')
  128. document.body.appendChild(iframe);
  129. return iframe
  130. } else if (isWap2App.value) {
  131. const style = {
  132. webviewBGTransparent: true,
  133. opacity: 0,
  134. render: 'always',
  135. zindex: -1,
  136. width: '1px',
  137. height: '1px',
  138. top:'0px',
  139. left:'0px',
  140. additionalHttpHeaders:{
  141. Referer: config.paySiteUrl,
  142. }
  143. };
  144. const wv = plus.webview.create(h5url, 'wechatPayWebview', style);
  145. wv.addEventListener('loaded', (e) => {
  146. checkOrderStatus();
  147. }, false);
  148. wv.show();
  149. return wv;
  150. }
  151. }
  152. const checkOrderStatus = async function () {
  153. if (!outTradeNo.value) return
  154. const {pause} = useIntervalFn(() => {
  155. const no = outTradeNo.value // double check
  156. if (!no) return pause()
  157. if (queryTimes-- <= 0) {
  158. triggerTimeout('支付超时')
  159. return pause();
  160. }
  161. // 由于这个请示不是规范返回,request配置为 custom: {toast: false}
  162. queryOrder({outTradeNo: no})
  163. .then()
  164. .catch(({tradeState}) => {
  165. console.log(no, tradeState)
  166. let eventArgs = []
  167. switch (tradeState) {
  168. case paymentState.NOTPAY:
  169. // user payment not complete, do nothing
  170. // waiting for next order check
  171. break;
  172. case paymentState.SUCCESS:
  173. // success
  174. outTradeNo.value = ''
  175. pause()
  176. eventArgs = ['交易成功', no]
  177. triggerSucceed(...eventArgs)
  178. break
  179. case paymentState.CLOSED:
  180. // timeout
  181. outTradeNo.value = ''
  182. pause()
  183. eventArgs = ['支付超时', no]
  184. triggerTimeout(...eventArgs)
  185. break
  186. default:
  187. // unexpected status
  188. outTradeNo.value = ''
  189. pause()
  190. eventArgs = [`未知交易状态:${tradeState}`, no]
  191. triggerFailed(...eventArgs)
  192. break
  193. }
  194. });
  195. }, interval)
  196. }
  197. const triggerSucceed = function () {
  198. lock = false
  199. if (func(options?.onSucceed)) options.onSucceed(...arguments)
  200. else orderSucceed.trigger(...arguments)
  201. }
  202. const triggerTimeout = function () {
  203. lock = false
  204. if (func(options?.onTimeout)) options.onTimeout(...arguments)
  205. orderTimeout.trigger(...arguments)
  206. }
  207. const triggerFailed = function () {
  208. lock = false
  209. if (func(options?.onFailed)) options.onFailed(...arguments)
  210. orderFailed.trigger(...arguments)
  211. }
  212. const clearResultFrame = function () {
  213. const { isH5, isWap2App } = useEnvStore();
  214. if (iframe) {
  215. if (isH5.value) {
  216. document.body.removeChild(iframe)
  217. } else if (isWap2App.value) {
  218. iframe.close();
  219. }
  220. iframe = null;
  221. }
  222. }
  223. return {
  224. price,
  225. payment,
  226. iosPayment,
  227. onOrderSucceed: orderSucceed.on,
  228. onOrderFailed: orderFailed.on,
  229. onOrderTimeout: orderTimeout.on
  230. }
  231. }