Files
Aida_Purchaser_Front/src/views/pay/payment.vue

369 lines
9.5 KiB
Vue
Raw Normal View History

2026-05-28 11:43:24 +08:00
<template>
2026-05-28 14:56:59 +08:00
<div class="payment">
<div class="header" @click="onBack">
<span class="icon"><svg-icon name="back" size="30" /></span>
<span class="text">Payment Details</span>
</div>
2026-05-29 14:55:54 +08:00
<!-- 未支付 -->
<template v-if="paymentStatus !== ORDER_STATUS.SUCCESS">
<div class="paylist">
<div class="item">
<img src="@/assets/images/pay/stripe.png" alt="" />
<span>Credit / Debit Card</span>
</div>
2026-05-28 14:56:59 +08:00
</div>
2026-05-29 14:55:54 +08:00
<div class="agreement">
<el-checkbox v-model="agreement">
<div class="text">
I agree to the <span>Terms & Conditions</span> and
<span>Privacy Policy</span>. All digital item sales are final and
non-refundable.
</div></el-checkbox
>
2026-05-28 14:56:59 +08:00
</div>
2026-05-29 14:55:54 +08:00
<div class="title">
<span class="icon"><svg-icon name="card" size="20" /></span>
<span class="text">Pay with Paypal</span>
2026-05-28 14:56:59 +08:00
</div>
2026-05-29 14:55:54 +08:00
<template v-if="!query.paymentId">
<div class="tip">
You'll be redirected to a Paypal popup to log in and confirm. No card details
are shared with Stylish Parade PayPal handles all payment security.
</div>
<div class="buttons">
<button custom="black" @click="onPayWith">
<span class="text">Pay with</span>
<span class="icon pay"><svg-icon name="pay-stripe" /></span>
</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">Cancel</span>
</div>
</template>
<!-- 已支付等待确认 -->
<template v-if="query.paymentId">
<div class="tip">
Please keep the window open until the payment is completed. If you are to open
the payment window, please check your browser settings to see if pop-ups are
being blocked. Points may be delayed after successful payment. Please wait 1-3
minutes and click the credits refresh button.
</div>
<div class="buttons">
<button custom="black" @click="getOrderStatus">I Have Completed payment</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">Back</span>
</div>
</template>
2026-05-28 14:56:59 +08:00
</template>
2026-05-29 14:55:54 +08:00
<!-- 已支付 -->
2026-05-29 14:25:24 +08:00
<template v-if="paymentStatus === ORDER_STATUS.SUCCESS">
2026-05-28 14:56:59 +08:00
<div class="success">
<img src="@/assets/images/pay/success.png" alt="" />
<div class="title">Purchase Successful!</div>
<div class="tip">
Your digital items are now available and have been saved in Personal Center My
Wardrobe.
</div>
</div>
2026-05-29 13:52:28 +08:00
<div class="progres" v-if="downloadInfo.status !== DOWNLOAD_STATUS.null">
<el-progress
:text-inside="true"
:percentage="downloadInfo.progress * 100"
:status="progressStatus"
:duration="0.3"
>
<span class="text"
>{{ FormatBytes(downloadInfo.loaded, { unitBig: true }) }} /
{{ FormatBytes(downloadInfo.total, { unitBig: true }) }}</span
>
</el-progress>
</div>
2026-05-28 14:56:59 +08:00
<div class="buttons">
2026-05-29 13:52:28 +08:00
<button custom="black" @click="handleDownloadAllAssets">download all Assets</button>
2026-05-28 14:56:59 +08:00
<button custom="black-box">
<span class="icon"><svg-icon name="order-file" size="18" /></span>
<span class="text">Export Invoice</span>
</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">Continue Shopping</span>
</div>
</template>
</div>
2026-05-28 11:43:24 +08:00
</template>
<script setup lang="ts">
2026-05-29 13:52:28 +08:00
import { FormatBytes } from '@/utils/tools'
import { fetchDownloadItemsByGet } from '@/api/user'
2026-05-28 11:43:24 +08:00
import { computed, onMounted, ref } from 'vue'
2026-05-29 14:25:24 +08:00
import { CreateOrder, ORDER_STATUS, GetOrderStatus } from '@/api/shoppingCart'
import { useRouter, useRoute } from 'vue-router'
2026-05-28 14:56:59 +08:00
import { ElMessage } from 'element-plus'
const onBack = () => router.back()
2026-05-29 14:25:24 +08:00
const router = useRouter()
const route = useRoute()
const query = computed(() => route.query)
2026-05-28 11:43:24 +08:00
const props = defineProps({
ids: {
type: Array,
default: () => []
}
})
2026-05-29 14:55:54 +08:00
onMounted(() => {
if (query.value.paymentLink && query.value.paymentId) {
createOrderCall({
paymentLink: query.value.paymentLink,
paymentId: query.value.paymentId
})
}
})
2026-05-28 14:56:59 +08:00
const agreement = ref(false)
2026-05-29 14:25:24 +08:00
const paymentStatus = ref(ORDER_STATUS.PENDING)
2026-05-28 14:56:59 +08:00
const onPayWith = () => {
if (!agreement.value) {
return ElMessage.warning('Please agree to the Terms & Conditions and Privacy Policy.')
}
const ids = [...props.ids]
if (ids.length === 0) return
2026-05-29 14:55:54 +08:00
CreateOrder(ids, true).then(createOrderCall)
2026-05-29 14:25:24 +08:00
}
2026-05-29 14:55:54 +08:00
const createOrderCall = (res: any) => {
const url = res.paymentLink
const paymentId = res.paymentId
openView(url)
router.replace({
query: {
2026-05-29 15:30:13 +08:00
list: query.value.list,
2026-05-29 14:55:54 +08:00
paymentId
}
})
2026-05-28 14:56:59 +08:00
}
const getOrderStatus = () => {
2026-05-29 14:25:24 +08:00
const paymentId = query.value.paymentId as string
GetOrderStatus(paymentId).then((res: any) => {
2026-05-29 14:55:54 +08:00
const status = res === null ? ORDER_STATUS.PENDING : (res as number)
paymentStatus.value = status
if (status !== ORDER_STATUS.SUCCESS) {
ElMessage.warning('订单未支付')
2026-05-29 14:25:24 +08:00
}
})
2026-05-28 14:56:59 +08:00
}
2026-05-29 14:25:24 +08:00
const openView = (url, width = 600, height = 800) => {
2026-05-29 13:52:28 +08:00
const left = (screen.width - width) / 2
const top = (screen.height - height) / 2
window.open(
url,
'_blank',
'width=' + width + ', height=' + height + ', left=' + left + ', top=' + top
)
}
const DOWNLOAD_STATUS = {
null: 0, // 未开始
dowloading: 1, // 下载中
success: 2, // 下载成功
failed: 3 // 下载失败
}
const downloadInfo = ref({
progress: 0,
status: DOWNLOAD_STATUS.null,
total: 0,
loaded: 0
})
const progressStatus = computed(() => {
if (downloadInfo.value.status === DOWNLOAD_STATUS.dowloading) {
return 'warning'
} else if (downloadInfo.value.status === DOWNLOAD_STATUS.success) {
return 'success'
} else if (downloadInfo.value.status === DOWNLOAD_STATUS.failed) {
return 'exception'
} else {
return 'warning'
}
})
const handleDownloadAllAssets = () => {
const ids = props.ids as string[]
downloadInfo.value.status = DOWNLOAD_STATUS.dowloading
downloadInfo.value.progress = 0
fetchDownloadItemsByGet({ ids }, (event) => {
downloadInfo.value.progress = event.progress
downloadInfo.value.loaded = event.loaded
downloadInfo.value.total = event.total
})
.then((res) => {
2026-05-29 14:25:24 +08:00
console.log(res)
2026-05-29 13:52:28 +08:00
const disposition = res.headers['content-disposition']
const fileName = disposition?.split('filename=')[1]?.replace(/"/g, '') || 'download.zip'
const blob = res.data
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
const timestamp = new Date().getTime()
link.download = fileName || `wardrobe_download_${timestamp}.zip`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
})
.catch((error) => {
downloadInfo.value.status = DOWNLOAD_STATUS.failed
})
.finally(() => {
downloadInfo.value.progress = 1
downloadInfo.value.status = DOWNLOAD_STATUS.success
})
}
2026-05-28 11:43:24 +08:00
</script>
<style lang="less" scoped>
2026-05-28 14:56:59 +08:00
.payment {
display: flex;
flex-direction: column;
> .header {
display: flex;
align-items: center;
gap: 2rem;
cursor: pointer;
user-select: none;
> .text {
font-family: KaiseiOpti-Bold;
font-size: 4rem;
}
}
> .paylist {
margin: 4rem 0 0 4rem;
padding: 2.7rem 0;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 0.1rem solid #c4c4c4;
> .item {
display: flex;
flex-direction: column;
align-items: center;
> img {
width: auto;
height: 3rem;
margin-bottom: 1.6rem;
}
> span {
font-family: KaiseiOpti-Regular;
font-size: 1.2rem;
color: #585858;
}
}
}
> .agreement {
margin-top: 2.4rem;
margin-left: 2rem;
margin-bottom: 10rem;
display: flex;
> .el-checkbox {
--el-checkbox-font-size: 1.4rem;
--el-checkbox-input-width: 1.4rem;
--el-checkbox-input-height: 1.4rem;
--el-checkbox-checked-bg-color: #000;
--el-checkbox-checked-input-border-color: #000;
--el-checkbox-input-border: 0.1rem solid #c4c4c4;
--el-checkbox-bg-color: #fff;
--el-checkbox-border-radius: 0;
}
.text {
color: #666;
word-break: break-word;
white-space: pre-wrap;
> * {
font-family: KaiseiOpti-Bold;
text-decoration: underline;
color: #232323;
}
}
}
> .title {
display: flex;
align-items: center;
gap: 1rem;
}
> .tip {
margin: 60px auto;
max-width: 59rem;
background: #f6f6f6;
border-left: 0.2rem solid #979797;
padding: 1.5rem 2rem;
font-size: 1.4rem;
line-height: 2.5rem;
color: #585858;
}
> .buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.8rem;
gap: 1.2rem;
> button {
min-width: 28rem;
height: 5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.8rem;
text-transform: uppercase;
> .icon.pay {
width: 7rem;
--svg-icon-width: auto;
--svg-icon-height: 2rem;
}
}
> span {
user-select: none;
cursor: pointer;
font-size: 1.4rem;
color: #979797;
}
}
> .success {
margin: 12rem auto;
max-width: 39rem;
display: flex;
align-items: center;
flex-direction: column;
> img {
width: 19.8rem;
height: auto;
margin-bottom: 2.4rem;
}
> .title {
font-size: 2rem;
margin-bottom: 0.8rem;
}
> .tip {
font-family: KaiseiOpti-Regular;
font-size: 1.4rem;
color: #666;
text-align: center;
}
}
2026-05-29 13:52:28 +08:00
> .progres {
margin: 2rem auto;
max-width: 50rem;
width: 100%;
.text {
font-size: 1.4rem;
line-height: 2.2rem;
color: #000;
}
&:deep(.el-progress) {
.el-progress-bar__outer {
height: 2.2rem !important;
}
}
}
2026-05-28 11:43:24 +08:00
}
</style>