shinnosuke
5 天以前 e52934f6527027bd6008c2ade61c3083159efd4a
rencaika/pages/payment-confirm/payment-confirm.uvue
@@ -30,9 +30,9 @@
                  <view class="coupon-value"><text>{{couponText}}</text><text class="chevron">›</text></view>
               </view>
               <view class="price-item"><text class="price-label">原价</text><text class="price-value">¥ {{originalAmountText}}</text></view>
               <view class="price-item"><text class="price-label">优惠金额</text><text class="price-value">-¥ {{discountAmountText}}</text></view>
               <view class="price-item"><text class="price-label">优惠金额</text><text class="price-value">-¥ {{couponAmountText}}</text></view>
               <view class="price-item"><text class="price-label">用户实付</text><text class="price-value strong">¥ {{actualPayAmountText}}</text></view>
               <view class="price-item"><text class="price-label">补贴金额</text><text class="price-value">¥ 0.00</text></view>
               <view class="price-item"><text class="price-label">补贴金额</text><text class="price-value">¥ {{subsidyAmountText}}</text></view>
            </view>
            <view class="actual-payment">
@@ -40,8 +40,8 @@
               <text class="actual-label">实际支付</text>
            </view>
            <view class="confirm-button" hover-class="confirm-button-active" @tap="confirmPayment">
               <text>确认支付</text>
            <view class="confirm-button" :class="isSubmitting ? 'confirm-button-disabled' : ''" hover-class="confirm-button-active" @tap="confirmPayment">
               <text>{{isSubmitting ? '支付中...' : '确认支付'}}</text>
            </view>
            <view class="safe-bottom"></view>
         </view>
@@ -51,7 +51,7 @@
<script setup lang="uts">
   import { safeBack } from '../../utils/navigation.uts'
   import { createAppOrder, getOrderCouponQuote } from '../../services/portal.uts'
   import { abandonPendingOrderPayment, createAppOrder, createPendingOrderPayment, getOrderCouponQuote } from '../../services/portal.uts'
   import { resolveApiUrl } from '../../services/http.uts'
   import { getSession } from '../../services/session.uts'
@@ -63,28 +63,30 @@
   const merchantId = ref('')
   const productId = ref('')
   const orderId = ref('')
   const talentId = ref('')
   const couponText = ref('无')
   const couponAmount = ref(0.0)
   const productName = ref('【川味盛宴】川菜12人餐')
   const productImage = ref('/static/food-assets/food-1.jpg')
   const productImage = ref('/static/common/image-placeholder.png')
   const salePrice = ref(138.0)
   const originalAmount = ref(266.0)
   const productDiscount = ref('6折')
   const salePriceText = computed(() => salePrice.value.toFixed(2))
   const originalAmountText = computed(() => originalAmount.value.toFixed(2))
   const discountAmountText = ref('0.00')
   const couponAmountText = computed(() => couponAmount.value.toFixed(2))
   const subsidyAmountText = ref('0.00')
   const actualPayAmountText = ref('0.00')
   const createOrderIdempotencyKey = ref('')
   let quoteSequence = 0
   const applyProductById = (id: string) => {
      if (id == 'food-set-002') { productName.value = '【川香四溢】川菜4人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 118.0; originalAmount.value = 198.0; productDiscount.value = '6折'; return }
      if (id == 'food-set-002') { productName.value = '【川香四溢】川菜4人餐'; productImage.value = '/static/common/image-placeholder.png'; salePrice.value = 118.0; originalAmount.value = 198.0; productDiscount.value = '6折'; return }
      if (id == 'food-set-003') { productName.value = '【欢聚时光】招牌菜6人餐'; salePrice.value = 168.0; originalAmount.value = 248.0; productDiscount.value = '6.8折'; return }
      if (id == 'food-set-004') { productName.value = '【暖心小聚】特色菜双人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 88.0; originalAmount.value = 126.0; productDiscount.value = '7折'; return }
      if (id == 'food-set-004') { productName.value = '【暖心小聚】特色菜双人餐'; productImage.value = '/static/common/image-placeholder.png'; salePrice.value = 88.0; originalAmount.value = 126.0; productDiscount.value = '7折'; return }
      if (id == 'food-set-005') { productName.value = '【家庭聚餐】经典川菜8人餐'; salePrice.value = 238.0; originalAmount.value = 366.0; productDiscount.value = '6.5折'; return }
      if (id == 'food-set-006') { productName.value = '【人气必点】招牌菜4人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 128.0; originalAmount.value = 188.0; productDiscount.value = '6.8折'; return }
      if (id == 'food-set-006') { productName.value = '【人气必点】招牌菜4人餐'; productImage.value = '/static/common/image-placeholder.png'; salePrice.value = 128.0; originalAmount.value = 188.0; productDiscount.value = '6.8折'; return }
      if (id == 'food-set-007') { productName.value = '【周末欢聚】特色菜10人餐'; salePrice.value = 298.0; originalAmount.value = 498.0; productDiscount.value = '6折'; return }
      if (id == 'food-set-008') { productName.value = '【一人食】暖心工作餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 39.0; originalAmount.value = 49.0; productDiscount.value = '8折'; return }
      if (id == 'food-set-008') { productName.value = '【一人食】暖心工作餐'; productImage.value = '/static/common/image-placeholder.png'; salePrice.value = 39.0; originalAmount.value = 49.0; productDiscount.value = '8折'; return }
   }
   onLoad((options: any) => {
@@ -99,6 +101,8 @@
      if (options != null && options['merchantId'] != null) merchantId.value = options['merchantId'] as string
      if (options != null && options['productId'] != null) productId.value = options['productId'] as string
      if (options != null && options['orderId'] != null) orderId.value = options['orderId'] as string
      const session = getSession()
      if (session != null && session.talentId != null) talentId.value = '' + session.talentId
      applyProductById(productId.value)
      if (options != null && options['name'] != null) productName.value = decodeURIComponent(options['name'] as string)
      if (options != null && options['image'] != null) productImage.value = decodeURIComponent(options['image'] as string)
@@ -132,7 +136,7 @@
      if (!validOrderContext()) return
      const sequence = quoteSequence + 1
      quoteSequence = sequence
      getOrderCouponQuote(merchantId.value, productId.value, orderId.value, couponId, (data: any) => {
      getOrderCouponQuote(merchantId.value, productId.value, orderId.value, couponId, talentId.value, (data: any) => {
         if (sequence != quoteSequence || data == null) return
         productName.value = data.productName != null ? data.productName as string : productName.value
         if (data.coverUrl != null && data.coverUrl != '') productImage.value = resolveApiUrl(data.coverUrl as string)
@@ -141,7 +145,7 @@
         couponAmount.value = toAmount(data.couponAmount)
         couponText.value = data.couponLabel != null ? data.couponLabel as string : '无'
         productDiscount.value = data.discountText != null ? data.discountText as string : productDiscount.value
         discountAmountText.value = toAmount(data.discountAmount).toFixed(2)
         subsidyAmountText.value = toAmount(data.subsidyAmount).toFixed(2)
         actualPayAmountText.value = toAmount(data.payAmount).toFixed(2)
      }, (error) => {
         if (sequence != quoteSequence) return
@@ -176,25 +180,87 @@
      const selectedCouponId: any = uni.getStorageSync(couponStorageKey())
      const couponId = selectedCouponId != null ? '' + selectedCouponId : ''
      createAppOrder(merchantId.value, productId.value, couponId, createOrderIdempotencyKey.value, (data: any) => {
         isSubmitting.value = false
         uni.removeStorageSync(couponStorageKey())
         const realOrderNo = data != null && data.orderNo != null ? '' + data.orderNo : ''
         if (realOrderNo == '') {
            isSubmitting.value = false
            uni.showToast({ title: '订单创建失败,请稍后重试', icon: 'none' })
            return
         }
         if (data != null && data.paid === true) {
            isSubmitting.value = false
            uni.redirectTo({ url: '/pages/payment-success/payment-success?orderId=' + encodeURIComponent(realOrderNo) })
            return
         }
         uni.redirectTo({
            url: '/pages/pending-payment/pending-payment?orderId=' + encodeURIComponent(realOrderNo)
               + '&talentId=' + encodeURIComponent(talentId)
         })
         // 一次点击即支付:用户既然点了「确认支付」,就不要再让他到第二页点同一个按钮——
         // 建单之后立刻发起预支付并拉起收银台,只有放弃或失败才落到待支付页。
         startPayment(realOrderNo, talentId, data != null && data.priceVersion != null ? Number(data.priceVersion) : 1)
      }, (error) => {
         isSubmitting.value = false
         uni.showToast({ title: error.message, icon: 'none' })
      })
   }
   const startPayment = (orderNo: string, talentId: string, priceVersion: number) => {
      createPendingOrderPayment(orderNo, talentId, priceVersion, (data: any) => {
         if (data != null && data.paymentStatus == 'PAID') {
            openPaymentSuccess(orderNo)
            return
         }
         if (data != null && data.paymentStatus == 'PAYMENT_PROCESSING') {
            // 关单中或结果未定:落到待支付页由它等结论(那里会显示「支付处理中」并退避轮询)。
            openPendingPayment(orderNo, talentId, true)
            return
         }
         const params: any = data != null ? data.paymentParams : null
         if (params == null || params.timeStamp == null || params.nonceStr == null || params.package == null || params.paySign == null) {
            uni.showToast({ title: '支付参数获取失败,请在待支付页重试', icon: 'none' })
            openPendingPayment(orderNo, talentId, false)
            return
         }
         presentCashier(orderNo, talentId, params)
      }, (error: any) => {
         uni.showToast({ title: error.message, icon: 'none' })
         openPendingPayment(orderNo, talentId, false)
      })
   }
   const presentCashier = (orderNo: string, talentId: string, params: any) => {
      // #ifdef MP-WEIXIN
      uni.requestPayment({
         provider: 'wxpay',
         timeStamp: '' + params.timeStamp,
         nonceStr: '' + params.nonceStr,
         package: '' + params.package,
         signType: params.signType != null ? '' + params.signType : 'RSA',
         paySign: '' + params.paySign,
         success: () => openPendingPayment(orderNo, talentId, true),
         fail: (error: any) => {
            const message = error != null && error.errMsg != null ? '' + error.errMsg : ''
            if (message.indexOf('cancel') >= 0) {
               // 用户关掉了收银台:告诉服务端一声,让它立刻尝试关单(服务端不据此判定收款)。
               abandonPendingOrderPayment(orderNo, talentId, '', () => openPendingPayment(orderNo, talentId, false), () => openPendingPayment(orderNo, talentId, false))
               return
            }
            uni.showToast({ title: '支付未完成,请在待支付页继续', icon: 'none' })
            openPendingPayment(orderNo, talentId, false)
         }
      })
      // #endif
      // #ifndef MP-WEIXIN
      uni.showToast({ title: '请在微信小程序中完成支付', icon: 'none' })
      openPendingPayment(orderNo, talentId, false)
      // #endif
   }
   const openPendingPayment = (orderNo: string, talentId: string, waitingPayment: boolean) => {
      isSubmitting.value = false
      uni.redirectTo({
         url: '/pages/pending-payment/pending-payment?orderId=' + encodeURIComponent(orderNo)
            + '&talentId=' + encodeURIComponent(talentId)
            + (waitingPayment ? '&waiting=1' : '')
      })
   }
   const openPaymentSuccess = (orderNo: string) => {
      isSubmitting.value = false
      uni.redirectTo({ url: '/pages/payment-success/payment-success?orderId=' + encodeURIComponent(orderNo) })
   }
</script>
@@ -234,5 +300,6 @@
   .confirm-button { display: flex; width: 514rpx; height: 86rpx; margin: 146rpx auto 0; border-radius: 45rpx; background-image: linear-gradient(90deg, #FF4C32 0%, #FF841C 100%); justify-content: center; align-items: center; }
   .confirm-button text { color: #FFFFFF; font-size: 30rpx; font-weight: 600; }
   .confirm-button-active { opacity: .82; transform: scale(.99); }
   .confirm-button-disabled { opacity: .62; transform: none; }
   .safe-bottom { height: 44rpx; height: calc(44rpx + env(safe-area-inset-bottom)); }
</style>