| New file |
| | |
| | | import Taro, { getCurrentInstance } from '@tarojs/taro'; |
| | | import { Component } from 'react'; |
| | | import { connect } from 'react-redux'; |
| | | import { View, Text, Image, Button } from '@tarojs/components'; |
| | | import { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult } from '../../actions/cashier'; |
| | | import { getStorage } from '../../utils/tools'; |
| | | import { jumpTo } from '../../utils/jump'; |
| | | import { dispatchSubscribeCreateFree } from '../../actions/im'; |
| | | import { _UID, _CASHIER_SEARCH_TIME } from '../../constants/api'; |
| | | import CashierModal from './components/cashier-modal'; |
| | | |
| | | import './index.less'; |
| | | |
| | | type Props = { |
| | | cashier: any |
| | | dispatchGetOrderInfo: any |
| | | dispatchMakePay: any |
| | | dispatchGetResult: any |
| | | dispatchSubscribeCreateFree: any |
| | | } |
| | | |
| | | type State = { |
| | | orderData: any |
| | | payType: number | string |
| | | showModal: boolean |
| | | } |
| | | |
| | | const ENV = process.env.TARO_ENV; |
| | | |
| | | // @ts-ignore |
| | | @connect(() => ({}), { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult, dispatchSubscribeCreateFree }) |
| | | /** |
| | | * 收银台页面 |
| | | * |
| | | * 页面路径: /pages/cashier/index |
| | | * |
| | | * 页面参数 (router params): |
| | | * @param {string} token - 必填,支付中心预下单接口返回的支付凭证,用于查询订单信息和发起支付 |
| | | * @param {string} from - 选填,调用来源标识,决定支付成功后的行为: |
| | | * - 'miniapp': 触发 cashier:paySuccess 事件通知并返回上一页,适用于小程序内页面调起收银台的场景 |
| | | * - 'app': 展示支付成功弹窗(CashierModal),适用于 App 端 webview 调起的场景 |
| | | * - 其他/不传: 跳转到 returnUrl 对应的 webview 页面(默认行为) |
| | | * @param {string} carType - 选填,车型标识。传入后在微信端支付前会触发订阅消息授权弹窗 |
| | | * |
| | | * 跳转示例: |
| | | * jumpTo({ url: `/pages/cashier/index?token=${token}&from=miniapp`, type: 'nav' }) |
| | | */ |
| | | export default class Cashier extends Component<Props, State> { |
| | | /** 支付凭证,来源于支付中心预下单接口 */ |
| | | token: string; |
| | | /** 支付结果轮询定时器 */ |
| | | timer: any; |
| | | /** 剩余轮询次数 */ |
| | | checkTimes: number; |
| | | openId: string; |
| | | /** 支付接口返回的数据,包含 businessId、businessOrderId、returnUrl 等 */ |
| | | payData: any = {}; |
| | | loading = false; |
| | | $instance: any = getCurrentInstance(); |
| | | $app: any = Taro.getApp().$app; |
| | | /** 车型标识,传入后微信端会触发订阅消息授权 */ |
| | | carType: any; |
| | | /** 调用来源:'miniapp' | 'app' | '' */ |
| | | from: string; |
| | | /** onAppShow 监听句柄,用于银联支付回调检测 */ |
| | | appShowListener: any; |
| | | |
| | | constructor(props) { |
| | | super(props); |
| | | this.state = { |
| | | orderData: {}, |
| | | payType: '', |
| | | showModal: false, |
| | | }; |
| | | |
| | | // 解析页面参数 |
| | | this.token = this.$instance.router.params.token || ''; |
| | | this.carType = this.$instance.router.params.carType || ''; |
| | | this.from = this.$instance.router.params.from || ''; |
| | | this.timer = null; |
| | | this.checkTimes = 5; |
| | | this.appShowListener = null; |
| | | } |
| | | |
| | | componentDidMount() { |
| | | } |
| | | |
| | | componentWillUnmount() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | } |
| | | if (this.appShowListener) { |
| | | Taro.offAppShow(this.appShowListener); |
| | | } |
| | | } |
| | | |
| | | componentDidShow() { |
| | | this.$app.xpReportShow(); |
| | | this.$app.xpReportSend({ pageId: 'P10036', buttonId: 'B002' }); |
| | | this.getOrderInfo(); |
| | | } |
| | | |
| | | componentDidHide() { |
| | | this.$app.xpReportHide(); |
| | | } |
| | | |
| | | // 查询订单信息 |
| | | getOrderInfo = () => { |
| | | Taro.showLoading({ title: '正在加载...' }); |
| | | getStorage(_UID) |
| | | .then(res => { |
| | | return this.props.dispatchGetOrderInfo({ |
| | | token: this.token, |
| | | payScene: ENV === 'alipay' ? 9 : 6, |
| | | preUid: res, |
| | | }) |
| | | }) |
| | | .then(orderRes => { |
| | | let payType = this.state.payType; |
| | | const _payTypeList = orderRes && orderRes.payTypeList || []; |
| | | const payTypeList = _payTypeList.sort(a => a.payType === 10 ? -1 : 1) || []; |
| | | payTypeList.forEach(item => { |
| | | if (payType) { |
| | | return; |
| | | } |
| | | if (ENV === 'weapp') { |
| | | if (item.payType === 1 || item.payType === 11 || item.payType === 17 || item.payType === 19) { |
| | | payType = item.payType; |
| | | } |
| | | } else if (ENV === 'alipay') { |
| | | if (item.payType === 2 || item.payType === 10 || item.payType === 18 || item.payType === 11) { |
| | | payType = item.payType; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | this.setState({ |
| | | payType, |
| | | orderData: { |
| | | ...orderRes, |
| | | payTypeList, |
| | | }, |
| | | }) |
| | | }) |
| | | .catch(console.error) |
| | | .finally(() => Taro.hideLoading()); |
| | | |
| | | } |
| | | |
| | | // 支付按钮触发 |
| | | handlePayment = async () => { |
| | | if (this.loading) { |
| | | return; |
| | | } |
| | | this.loading = true; |
| | | |
| | | if (ENV === 'weapp' && this.carType) { |
| | | await this.requestSubscribeMessage(); |
| | | } |
| | | |
| | | const { payType } = this.state; |
| | | const { openId } = await this.$app.getIdData() || {}; |
| | | getStorage(_UID) |
| | | .then(res => { |
| | | return this.props.dispatchMakePay({ |
| | | token: this.token, |
| | | payScene: ENV === 'alipay' ? 9 : 6, |
| | | payType, |
| | | preUid: res, |
| | | wxOpenId: openId, |
| | | }); |
| | | }) |
| | | .then(payRes => { |
| | | if (payRes) { |
| | | this.payData = payRes; |
| | | if (payType === 19) { |
| | | // 银联云闪付支付 |
| | | this.unionPay(payRes.actionData); |
| | | } else if (ENV === 'alipay') { |
| | | this.aliPay(payRes.actionData && payRes.actionData.aliOrderInfo, payType); |
| | | } else { |
| | | this.wxPay(payRes.actionData); |
| | | } |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err) |
| | | }) |
| | | .then(() => this.loading = false); |
| | | } |
| | | |
| | | // 银联云闪付支付 |
| | | unionPay = (data) => { |
| | | // 根据后端返回的 actionData 结构调用银联支付 |
| | | // 具体参数需要根据银联 SDK 文档确定 |
| | | if (!data || !data.appId || !data.path) { |
| | | this.payFail(data) |
| | | return |
| | | } |
| | | Taro.navigateToMiniProgram({ |
| | | appId: data.appId, |
| | | path: data.path.indexOf('tn') > 0 ? data.path : `${data.path}?tn=${data.tn}`, |
| | | // extraData: { |
| | | // foo: 'bar' |
| | | // }, |
| | | // envVersion: 'trial', |
| | | success: () => { |
| | | const handleAppShow = (res) => { |
| | | if (res && res.scene === 1038) { |
| | | Taro.offAppShow(this.appShowListener); |
| | | this.paySuccess({}) |
| | | } |
| | | } |
| | | this.appShowListener = handleAppShow |
| | | Taro.onAppShow(handleAppShow) |
| | | }, |
| | | fail: this.payFail |
| | | }) |
| | | } |
| | | |
| | | // 微信支付 |
| | | wxPay = (data) => { |
| | | Taro.requestPayment({ |
| | | timeStamp: data.timeStamp, |
| | | nonceStr: data.nonceStr, |
| | | package: data.package, |
| | | signType: data.signType, |
| | | paySign: data.sign, |
| | | success: this.paySuccess, |
| | | fail: this.payFail, |
| | | }) |
| | | } |
| | | |
| | | // 支付宝支付 |
| | | aliPay = (tradeNO, payType) => { |
| | | const data = payType === 10 ? { orderStr: tradeNO } : { tradeNO }; |
| | | // @ts-ignore |
| | | // eslint-disable-next-line no-undef |
| | | my.tradePay({ |
| | | ...data, |
| | | success: this.paySuccess, |
| | | fail: this.payFail, |
| | | }) |
| | | } |
| | | |
| | | // 支付成功回调 |
| | | paySuccess = (data) => { |
| | | console.log('paySuccess:'); |
| | | console.log(data); |
| | | |
| | | clearInterval(this.timer); |
| | | this.checkTimes = _CASHIER_SEARCH_TIME; |
| | | |
| | | if (data) { |
| | | this.timer = setInterval(() => { |
| | | const { checkTimes } = this; |
| | | if (checkTimes > 0) { |
| | | this.props.dispatchGetResult({ |
| | | businessId: this.payData.businessId, |
| | | businessOrderId: this.payData.businessOrderId, |
| | | token: this.token, |
| | | }) |
| | | .then(checkRes => { |
| | | if (checkRes.payStatus === 1 || checkRes.payStatus === 5) { // 1支付成功,5芝麻分授权成功 |
| | | // 检查订单支付成功 |
| | | clearInterval(this.timer); |
| | | |
| | | if (this.from === 'app') { |
| | | this.setState({ showModal: true }); |
| | | return; |
| | | } |
| | | |
| | | if (this.from === 'miniapp') { |
| | | Taro.eventCenter.trigger('cashier:paySuccess', { |
| | | businessId: this.payData.businessId, |
| | | businessOrderId: this.payData.businessOrderId, |
| | | payStatus: checkRes.payStatus, |
| | | }); |
| | | jumpTo({ type: 'back' }); |
| | | return; |
| | | } |
| | | |
| | | jumpTo({ |
| | | url: `/pages/webpage/index?pageUrl=${encodeURIComponent(this.payData.returnUrl)}`, |
| | | type: 'red' |
| | | }) |
| | | } else { |
| | | // 未成功,继续轮询 |
| | | this.checkTimes--; |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err); |
| | | this.checkTimes--; |
| | | }); |
| | | } else if (this.timer) { |
| | | clearInterval(this.timer); |
| | | Taro.showToast({ |
| | | title: '状态异常,稍候查询', |
| | | icon: 'none', |
| | | }); |
| | | } |
| | | }, 1000); |
| | | } |
| | | } |
| | | |
| | | // 支付失败回调 |
| | | payFail = data => { |
| | | Taro.showToast({ |
| | | title: '支付失败', |
| | | icon: 'none', |
| | | }); |
| | | console.error(data); |
| | | } |
| | | |
| | | selectPayType = _payType => { |
| | | const { payType } = this.state; |
| | | this.setState({ payType: _payType === payType ? '' : _payType }); |
| | | } |
| | | |
| | | requestSubscribeMessage = () => { |
| | | return new Promise((resolve) => { |
| | | let tmplIds = [ 'bXVVcmoaI6uZ4aq6M38wRAMfM2tUVLV07WC9fEuyi3k', '0kLTB3EVD-Y9f2gqNhsMf3lDfoek2YdO-UiFsYyJE7o', 't8bRrfiTheK_n-XbPm5XfQ-XKhpOF1ZFD0g0Rk-4jis' ]; |
| | | Taro.requestSubscribeMessage({ |
| | | tmplIds, |
| | | success: async res => { |
| | | const acceptId: any = []; |
| | | tmplIds.forEach(item => { |
| | | if (res[item] === 'accept') { |
| | | acceptId.push(item); |
| | | } |
| | | }); |
| | | if (acceptId.length) { |
| | | const { openId } = await this.$app.getIdData() || {}; |
| | | this.props.dispatchSubscribeCreateFree({ |
| | | scene: 'NOT_G6_PREORDER_PAYMENT', |
| | | openId: openId, |
| | | templateIds: acceptId |
| | | }) |
| | | .then(codeRes => { |
| | | console.log(codeRes); |
| | | }) |
| | | .catch(console.error); |
| | | } |
| | | }, |
| | | complete: () => { |
| | | resolve({}); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | render() { |
| | | const { orderData, payType, showModal } = this.state; |
| | | const payTypeList = orderData && orderData.payTypeList || []; |
| | | |
| | | return ( |
| | | <View className='cashier-page'> |
| | | |
| | | <View className='order-info'> |
| | | <View className='amount-name'>待支付金额</View> |
| | | <View className='amount-value'> |
| | | <Text>¥ </Text> |
| | | <Text className='count'>{orderData.amount ? (orderData.amount / 100).toFixed(2) : ''}</Text> |
| | | </View> |
| | | |
| | | <View className='option'> |
| | | <Text className='option-name'>订单号</Text> |
| | | <Text className='option-value'>{orderData.bizOrderId}</Text> |
| | | </View> |
| | | <View className='option'> |
| | | <Text className='option-name'>订单标题</Text> |
| | | <Text className='option-value'>{orderData.title}</Text> |
| | | </View> |
| | | </View> |
| | | |
| | | {this.from !== 'app' && ( |
| | | <View className='list-title'>支付方式</View> |
| | | )} |
| | | |
| | | {ENV === 'alipay' && this.from !== 'app' && ( |
| | | <View className='pay-list'> |
| | | {payTypeList.map(item => { |
| | | //普通支付宝:2, 芝麻分:10, 建行在支付宝:18, 银联:11 |
| | | if (item.payType === 2 || item.payType === 18 || item.payType === 11) { |
| | | return ( |
| | | <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> |
| | | <Image className='icon' src={require('./assets/ic_zfb.png')}></Image> |
| | | <View className='name'>支付宝</View> |
| | | {payType === item.payType ? ( |
| | | <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> |
| | | ) : ( |
| | | <Image className='ic-right' src={require('./assets/ic_se_default.png')}></Image> |
| | | )} |
| | | </View> |
| | | ); |
| | | } |
| | | if (item.payType === 10) { |
| | | return ( |
| | | <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> |
| | | <View className='name'> |
| | | <Image className='logo' src={require('./assets/ic_zmf.png')}></Image> |
| | | <Text className='text'>芝麻分≥600有机会免意向金</Text> |
| | | </View> |
| | | {payType === item.payType ? ( |
| | | <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> |
| | | ) : ( |
| | | <Image className='ic-right' src={require('./assets/ic_se_default.png')}></Image> |
| | | )} |
| | | </View> |
| | | ); |
| | | } |
| | | })} |
| | | </View> |
| | | )} |
| | | |
| | | {ENV === 'weapp' && this.from !== 'app' && ( |
| | | <View className='pay-list'> |
| | | {payTypeList.map(item => { |
| | | //普通微信:1, 建行在微信:17 |
| | | if (item.payType === 1 || item.payType === 11 || item.payType === 17) { |
| | | return ( |
| | | <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> |
| | | <Image className='icon' src={require('./assets/ic_wx.png')}></Image> |
| | | <View className='name'>微信支付</View> |
| | | {payType === item.payType ? ( |
| | | <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> |
| | | ) : ( |
| | | <Image className='ic-right' src={require('./assets/ic_se_default.png')}></Image> |
| | | )} |
| | | </View> |
| | | ); |
| | | } |
| | | if (item.payType === 19) { |
| | | return ( |
| | | <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> |
| | | <Image className='icon' src={item.icon}></Image> |
| | | <View className='name'>{item.description}</View> |
| | | {payType === item.payType ? ( |
| | | <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> |
| | | ) : ( |
| | | <Image className='ic-right' src={require('./assets/ic_se_default.png')}></Image> |
| | | )} |
| | | </View> |
| | | ); |
| | | } |
| | | })} |
| | | </View> |
| | | )} |
| | | |
| | | <View className='btn-wrap'> |
| | | {payType ? ( |
| | | <Button key={payType} className='btn-pay' onClick={this.handlePayment}>确认支付</Button> |
| | | ) : ( |
| | | <Button key='disabled' className='btn-pay disabled'>请选择支付方式</Button> |
| | | )} |
| | | </View> |
| | | |
| | | {/* app来源时出现弹窗 */} |
| | | <CashierModal show={showModal} /> |
| | | |
| | | </View> |
| | | ) |
| | | } |
| | | } |