344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
const {
|
|
request,
|
|
ensureLogin,
|
|
cents,
|
|
clientRequestId,
|
|
requestWechatPayment,
|
|
} = require('../../utils/api.js')
|
|
|
|
function defaultStartAt() {
|
|
const date = new Date(Date.now() + 30 * 60 * 1000)
|
|
date.setSeconds(0, 0)
|
|
return date
|
|
}
|
|
|
|
function isoMinutesAfter(date, minutes) {
|
|
return new Date(date.getTime() + minutes * 60 * 1000).toISOString()
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
storeId: '',
|
|
roomId: '',
|
|
loading: false,
|
|
errorMessage: '',
|
|
successMessage: '',
|
|
durationMinutes: 120,
|
|
pricingMode: 'HOURLY',
|
|
quote: null,
|
|
order: null,
|
|
payment: null,
|
|
couponGrantId: '',
|
|
packageHoldingId: '',
|
|
packageMinutes: '',
|
|
packageCreditYuan: '',
|
|
benefitLoading: false,
|
|
availableCoupons: [],
|
|
availablePackages: [],
|
|
selectedCouponName: '',
|
|
selectedPackageName: '',
|
|
},
|
|
onLoad(options) {
|
|
this.setData({
|
|
storeId: options.storeId || '',
|
|
roomId: options.roomId || '',
|
|
})
|
|
this.loadBenefitOptions()
|
|
},
|
|
|
|
async loadBenefitOptions() {
|
|
this.setData({ benefitLoading: true })
|
|
try {
|
|
await ensureLogin()
|
|
const response = await request('/profile/benefits')
|
|
this.setData({
|
|
availableCoupons: (response.data.coupons || [])
|
|
.filter((item) => canUseCoupon(item, this.data.storeId, this.data.roomId))
|
|
.map(formatCouponOption),
|
|
availablePackages: (response.data.packages || [])
|
|
.filter((item) => canUsePackage(item, this.data.storeId, this.data.roomId))
|
|
.map(formatPackageOption),
|
|
})
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '权益加载失败' })
|
|
} finally {
|
|
this.setData({ benefitLoading: false })
|
|
}
|
|
},
|
|
|
|
selectCoupon(event) {
|
|
const couponGrantId = event.currentTarget.dataset.id || ''
|
|
const current = this.data.couponGrantId === couponGrantId
|
|
const selected = current
|
|
? null
|
|
: this.data.availableCoupons.find((item) => item.couponGrantId === couponGrantId)
|
|
this.setData({
|
|
couponGrantId: current ? '' : couponGrantId,
|
|
selectedCouponName: selected ? selected.name : '',
|
|
order: null,
|
|
payment: null,
|
|
successMessage: '',
|
|
errorMessage: '',
|
|
})
|
|
},
|
|
|
|
selectPackage(event) {
|
|
const packageHoldingId = event.currentTarget.dataset.id || ''
|
|
const current = this.data.packageHoldingId === packageHoldingId
|
|
const selected = current
|
|
? null
|
|
: this.data.availablePackages.find((item) => item.packageHoldingId === packageHoldingId)
|
|
this.setData({
|
|
packageHoldingId: current ? '' : packageHoldingId,
|
|
selectedPackageName: selected ? selected.name : '',
|
|
packageMinutes: selected && selected.remainingMinutes > 0
|
|
? String(Math.min(selected.remainingMinutes, this.data.durationMinutes))
|
|
: '',
|
|
packageCreditYuan: selected && selected.remainingAmountCents > 0
|
|
? yuanInput(Math.min(
|
|
selected.remainingAmountCents,
|
|
this.data.quote?.totalCents || selected.remainingAmountCents
|
|
))
|
|
: '',
|
|
order: null,
|
|
payment: null,
|
|
successMessage: '',
|
|
errorMessage: '',
|
|
})
|
|
},
|
|
|
|
updatePackageUsage(event) {
|
|
const field = event.currentTarget.dataset.field
|
|
if (!field) return
|
|
this.setData({
|
|
[field]: event.detail.value,
|
|
order: null,
|
|
payment: null,
|
|
successMessage: '',
|
|
errorMessage: '',
|
|
})
|
|
},
|
|
|
|
setDuration(event) {
|
|
this.setData({
|
|
durationMinutes: Number(event.currentTarget.dataset.minutes),
|
|
quote: null,
|
|
order: null,
|
|
payment: null,
|
|
errorMessage: '',
|
|
successMessage: '',
|
|
})
|
|
},
|
|
|
|
async quote() {
|
|
await this.withRequest(async () => {
|
|
const payload = this.pricingPayload()
|
|
const response = await request('/pricing/quote', { method: 'POST', data: payload })
|
|
this.setData({
|
|
quote: {
|
|
...response.data,
|
|
totalText: cents(response.data.totalCents),
|
|
depositText: cents(response.data.depositCents),
|
|
},
|
|
successMessage: '报价已生成',
|
|
})
|
|
})
|
|
},
|
|
|
|
async reserve() {
|
|
await this.withRequest(async () => {
|
|
const payload = this.pricingPayload()
|
|
const response = await request('/orders/reserve', { method: 'POST', data: payload })
|
|
this.setData({
|
|
order: response.data,
|
|
successMessage: response.data.benefitReservation
|
|
? '时段已预占,权益已冻结,请继续支付'
|
|
: '时段已预占,请继续支付',
|
|
})
|
|
})
|
|
},
|
|
|
|
async createTestPayment() {
|
|
if (!this.data.order || !this.data.order.orderId) {
|
|
this.setData({ errorMessage: '请先预占订单' })
|
|
return
|
|
}
|
|
|
|
function canUseCoupon(item, storeId, roomId) {
|
|
return item.status === 'AVAILABLE'
|
|
&& Number(item.remainingUses || 0) > 0
|
|
&& (!item.storeId || String(item.storeId) === String(storeId))
|
|
&& (!item.roomId || String(item.roomId) === String(roomId))
|
|
}
|
|
|
|
function canUsePackage(item, storeId, roomId) {
|
|
return item.status === 'ACTIVE'
|
|
&& (Number(item.remainingMinutes || 0) > 0 || Number(item.remainingAmountCents || 0) > 0)
|
|
&& (!item.storeId || String(item.storeId) === String(storeId))
|
|
&& (!item.roomId || String(item.roomId) === String(roomId))
|
|
}
|
|
|
|
function formatCouponOption(item) {
|
|
const isTime = item.couponType === 'TIME'
|
|
return {
|
|
...item,
|
|
valueText: isTime ? `${item.timeMinutes || 0}分钟` : cents(item.discountAmountCents),
|
|
thresholdText: Number(item.minOrderAmountCents || 0) > 0
|
|
? `满${cents(item.minOrderAmountCents)}可用`
|
|
: '无门槛',
|
|
scopeText: scopeText(item),
|
|
}
|
|
}
|
|
|
|
function formatPackageOption(item) {
|
|
return {
|
|
...item,
|
|
remainingAmountText: cents(item.remainingAmountCents),
|
|
scopeText: scopeText(item),
|
|
}
|
|
}
|
|
|
|
function scopeText(item) {
|
|
if (item.roomId) return `房间 ${item.roomId}`
|
|
if (item.roomCategoryId) return `房型 ${item.roomCategoryId}`
|
|
if (item.storeId) return `门店 ${item.storeId}`
|
|
return item.holidayOnly ? '节假日' : '通用'
|
|
}
|
|
|
|
function yuanInput(centsValue) {
|
|
return (Number(centsValue || 0) / 100).toFixed(2)
|
|
}
|
|
await this.withRequest(async () => {
|
|
const response = await request('/payments', {
|
|
method: 'POST',
|
|
data: {
|
|
orderId: this.data.order.orderId,
|
|
provider: 'TEST',
|
|
clientRequestId: clientRequestId('miniapp-pay'),
|
|
},
|
|
})
|
|
this.setData({
|
|
payment: {
|
|
...response.data,
|
|
amountText: cents(response.data.amountCents),
|
|
},
|
|
successMessage: '测试支付单已创建',
|
|
})
|
|
})
|
|
},
|
|
|
|
async createWechatPayment() {
|
|
if (!this.data.order || !this.data.order.orderId) {
|
|
this.setData({ errorMessage: '请先预占订单' })
|
|
return
|
|
}
|
|
await this.withRequest(async () => {
|
|
const paymentResponse = await request('/payments', {
|
|
method: 'POST',
|
|
data: {
|
|
orderId: this.data.order.orderId,
|
|
provider: 'WECHAT',
|
|
clientRequestId: clientRequestId('miniapp-wxpay'),
|
|
},
|
|
})
|
|
const prepayResponse = await request('/pay/wechat/prepay', {
|
|
method: 'POST',
|
|
data: { paymentId: paymentResponse.data.paymentId },
|
|
})
|
|
await requestWechatPayment(prepayResponse.data)
|
|
this.setData({
|
|
payment: {
|
|
...paymentResponse.data,
|
|
amountText: cents(paymentResponse.data.amountCents),
|
|
},
|
|
successMessage: '微信支付已提交,请稍后查看订单状态',
|
|
})
|
|
})
|
|
},
|
|
|
|
async createBalancePayment() {
|
|
if (!this.data.order || !this.data.order.orderId) {
|
|
this.setData({ errorMessage: '请先预占订单' })
|
|
return
|
|
}
|
|
await this.withRequest(async () => {
|
|
const response = await request('/payments', {
|
|
method: 'POST',
|
|
data: {
|
|
orderId: this.data.order.orderId,
|
|
provider: 'BALANCE',
|
|
clientRequestId: clientRequestId('miniapp-balance'),
|
|
},
|
|
})
|
|
this.setData({
|
|
payment: {
|
|
...response.data,
|
|
amountText: cents(response.data.amountCents),
|
|
},
|
|
successMessage: '余额支付已完成',
|
|
})
|
|
})
|
|
},
|
|
|
|
async completeTestPayment() {
|
|
if (!this.data.payment || !this.data.payment.paymentId) {
|
|
this.setData({ errorMessage: '请先创建测试支付单' })
|
|
return
|
|
}
|
|
await this.withRequest(async () => {
|
|
await request(`/payments/${encodeURIComponent(this.data.payment.paymentId)}/test-complete`, {
|
|
method: 'POST',
|
|
data: {
|
|
callbackId: clientRequestId('miniapp-callback'),
|
|
amountCents: this.data.payment.amountCents,
|
|
},
|
|
})
|
|
this.setData({ successMessage: '测试支付已完成,生产环境将切换微信支付' })
|
|
})
|
|
},
|
|
|
|
openOrder(event) {
|
|
const orderId = event.currentTarget.dataset.orderId
|
|
if (!orderId) return
|
|
wx.navigateTo({ url: `/pages/orders/detail?orderId=${encodeURIComponent(orderId)}` })
|
|
},
|
|
|
|
pricingPayload() {
|
|
const start = defaultStartAt()
|
|
return {
|
|
roomId: this.data.roomId,
|
|
startAt: start.toISOString(),
|
|
endAt: isoMinutesAfter(start, this.data.durationMinutes),
|
|
pricingMode: this.data.pricingMode,
|
|
benefits: this.benefitsPayload(),
|
|
}
|
|
},
|
|
|
|
benefitsPayload() {
|
|
const couponGrantId = String(this.data.couponGrantId || '').trim()
|
|
const packageHoldingId = String(this.data.packageHoldingId || '').trim()
|
|
if (!couponGrantId && !packageHoldingId) return undefined
|
|
const packageMinutes = Number(this.data.packageMinutes || 0)
|
|
const packageCreditCents = Math.round(Number(this.data.packageCreditYuan || 0) * 100)
|
|
return {
|
|
couponGrantId: couponGrantId || null,
|
|
packageHoldingId: packageHoldingId || null,
|
|
packageMinutes: packageMinutes > 0 ? packageMinutes : 0,
|
|
packageCreditCents: packageCreditCents > 0 ? packageCreditCents : 0,
|
|
clientRequestId: clientRequestId('miniapp-benefit'),
|
|
}
|
|
},
|
|
|
|
async withRequest(work) {
|
|
this.setData({ loading: true, errorMessage: '', successMessage: '' })
|
|
try {
|
|
await ensureLogin()
|
|
await work()
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '操作失败' })
|
|
} finally {
|
|
this.setData({ loading: false })
|
|
}
|
|
},
|
|
})
|