feat(payment): 集成 Stripe 支付功能
- 新增 Stripe 支付相关的组件和页面 - 实现了支付流程的初始化、表单提交和结果处理 - 优化了支付页面的样式和交互 - 更新了部分 API 接口以支持新的支付功能
This commit is contained in:
parent
70fa0eb135
commit
d1579247e1
@ -81,7 +81,7 @@ export async function fddCheck(data) {
|
||||
export async function createBuyOrder(data) {
|
||||
|
||||
return await request( {
|
||||
url:'/api/v1/m/auction/createBuyOrder',
|
||||
url:'/api/v1/m/auction/createBuyOrder/v2',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
|
331
app/components/stripe/CheckoutPage.vue
Normal file
331
app/components/stripe/CheckoutPage.vue
Normal file
@ -0,0 +1,331 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV")
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }]
|
||||
const elements = ref(null)
|
||||
const paymentMessage = ref('')
|
||||
const isLoading = ref(false)
|
||||
const showSpinner = ref(false)
|
||||
|
||||
async function initialize() {
|
||||
const clientSecret = 'pi_3QxII1AB1Vm8VfJq1OyR3bkz_secret_d8fgL53X6T3MQpYfi2lRH3V1F'
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
}
|
||||
elements.value = stripe.elements({ appearance, clientSecret })
|
||||
|
||||
const paymentElementOptions = {
|
||||
layout: "accordion",
|
||||
}
|
||||
|
||||
const paymentElement = elements.value.create("payment", paymentElementOptions)
|
||||
paymentElement.mount("#payment-element")
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements: elements.value,
|
||||
confirmParams: {
|
||||
return_url: "http://localhost:4242/complete",
|
||||
},
|
||||
})
|
||||
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message)
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.")
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
function showMessage(messageText) {
|
||||
paymentMessage.value = messageText
|
||||
setTimeout(() => {
|
||||
paymentMessage.value = ''
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
isLoading.value = loading
|
||||
showSpinner.value = loading
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="payment-form" @submit="handleSubmit">
|
||||
<div id="payment-element">
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div>
|
||||
<span id="button-text" :class="{ hidden: showSpinner }">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" :class="{ hidden: !paymentMessage }">{{ paymentMessage }}</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form {
|
||||
width:100vw;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
/* 其他样式保持不变... */
|
||||
</style>
|
115
app/components/stripe/CompletePage.vue
Normal file
115
app/components/stripe/CompletePage.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV")
|
||||
const statusIcon = ref('')
|
||||
const statusText = ref('')
|
||||
const intentId = ref('')
|
||||
const intentStatus = ref('')
|
||||
const viewDetailsUrl = ref('')
|
||||
const iconColor = ref('')
|
||||
|
||||
const SuccessIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const ErrorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const InfoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
|
||||
<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
function setPaymentDetails(intent) {
|
||||
let status = "Something went wrong, please try again."
|
||||
let color = "#DF1B41"
|
||||
let icon = ErrorIcon
|
||||
|
||||
if (!intent) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
switch (intent.status) {
|
||||
case "succeeded":
|
||||
status = "Payment succeeded"
|
||||
color = "#30B130"
|
||||
icon = SuccessIcon
|
||||
break
|
||||
case "processing":
|
||||
status = "Your payment is processing."
|
||||
color = "#6D6E78"
|
||||
icon = InfoIcon
|
||||
break
|
||||
case "requires_payment_method":
|
||||
status = "Your payment was not successful, please try again."
|
||||
break
|
||||
}
|
||||
|
||||
iconColor.value = color
|
||||
statusIcon.value = icon
|
||||
statusText.value = status
|
||||
intentId.value = intent.id
|
||||
intentStatus.value = intent.status
|
||||
viewDetailsUrl.value = `https://dashboard.stripe.com/payments/${intent.id}`
|
||||
}
|
||||
|
||||
function setErrorState() {
|
||||
iconColor.value = "#DF1B41"
|
||||
statusIcon.value = ErrorIcon
|
||||
statusText.value = "Something went wrong, please try again."
|
||||
}
|
||||
|
||||
async function checkStatus() {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const clientSecret = urlParams.get("payment_intent_client_secret")
|
||||
|
||||
if (!clientSecret) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret)
|
||||
setPaymentDetails(paymentIntent)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="payment-status">
|
||||
<div id="status-icon" :style="{ backgroundColor: iconColor }" v-html="statusIcon"></div>
|
||||
<h2 id="status-text">{{ statusText }}</h2>
|
||||
<div id="details-table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="TableLabel">id</td>
|
||||
<td id="intent-id" class="TableContent">{{ intentId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="TableLabel">status</td>
|
||||
<td id="intent-status" class="TableContent">{{ intentStatus }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a :href="viewDetailsUrl" id="view-details" rel="noopener noreferrer" target="_blank">
|
||||
View details
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/>
|
||||
<path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/>
|
||||
</svg>
|
||||
</a>
|
||||
<NuxtLink id="retry-button" to="/checkout">Test another</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 将原来 checkout.css 中的样式复制到这里 */
|
||||
</style>
|
@ -49,16 +49,15 @@ const confirm=async ()=>{
|
||||
message.warning(t('collectCode.message.lotNoRequired'))
|
||||
return false
|
||||
}
|
||||
function validateNumber(num) {
|
||||
const numStr = String(num)
|
||||
return /^250\d{3}$/.test(numStr)
|
||||
function is25Format(num) {
|
||||
return /^25\d{3}$/.test(String(num));
|
||||
}
|
||||
if (!validateNumber(createForm.value.lotNo)){
|
||||
if (!is25Format(createForm.value.lotNo)){
|
||||
message.warning(t('collectCode.message.lotNoType'))
|
||||
return
|
||||
}
|
||||
|
||||
const res=await offlineQrcodeCreate({...createForm.value,price:String(createForm.value.price)})
|
||||
const res=await offlineQrcodeCreate({...createForm.value,price:String(createForm.value.price),lotNo:createForm.value.lotNo-25000})
|
||||
if (res.status===0){
|
||||
show.value=false
|
||||
onRefresh()
|
||||
|
@ -1,18 +1,19 @@
|
||||
<script setup>
|
||||
import CheckoutPage from '@/components/stripe/CheckoutPage.vue'
|
||||
import CompletePage from '@/components/stripe/CompletePage.vue'
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: 'Stripe支付'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const url=route.query.url??''
|
||||
|
||||
const key=route.query.key??''
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<iframe class="w-100vw h-100vh" src="/stripe/checkout.html?_ijt=klcciv1ggvbrm6h834l75lnnga"></iframe>
|
||||
<CheckoutPage/>
|
||||
<!-- <iframe class="w-100vw h-100vh" :src="`/stripe/checkout.html?key=${key}`"></iframe> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -71,9 +71,9 @@ const openShow = async (item) => {
|
||||
class="w-full object-cover rounded-4px"
|
||||
/>
|
||||
<div
|
||||
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
|
||||
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[55px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
|
||||
>
|
||||
LOT{{ item.index }}
|
||||
Lot{{ item.index }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-[8px]">
|
||||
|
342
app/pages/payment/checkoutPage/index.vue
Normal file
342
app/pages/payment/checkoutPage/index.vue
Normal file
@ -0,0 +1,342 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import {authStore} from "~/stores/auth/index.js";
|
||||
const {checkoutSessionUrl,payUid}= authStore()
|
||||
const config = useRuntimeConfig()
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: 'Stripe支付'
|
||||
})
|
||||
console.log('config.public.NUXT_PUBLIC_PKEY',config.public.NUXT_PUBLIC_PKEY);
|
||||
const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY)
|
||||
|
||||
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }]
|
||||
const elements = ref(null)
|
||||
const paymentMessage = ref('')
|
||||
const isLoading = ref(false)
|
||||
const showSpinner = ref(false)
|
||||
|
||||
async function initialize() {
|
||||
const clientSecret = checkoutSessionUrl.value
|
||||
console.log('clientSecret',clientSecret);
|
||||
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
}
|
||||
elements.value = stripe.elements({ appearance, clientSecret })
|
||||
|
||||
const paymentElementOptions = {
|
||||
layout: "accordion",
|
||||
}
|
||||
|
||||
const paymentElement = elements.value.create("payment", paymentElementOptions)
|
||||
paymentElement.mount("#payment-element")
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements: elements.value,
|
||||
confirmParams: {
|
||||
return_url: "http://192.168.88.68:3000/payment/result?orderNo="+payUid.value,
|
||||
},
|
||||
})
|
||||
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message)
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.")
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
function showMessage(messageText) {
|
||||
paymentMessage.value = messageText
|
||||
setTimeout(() => {
|
||||
paymentMessage.value = ''
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
isLoading.value = loading
|
||||
showSpinner.value = loading
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="payment-form" @submit="handleSubmit">
|
||||
<div id="payment-element">
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div>
|
||||
<span id="button-text" :class="{ hidden: showSpinner }">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" :class="{ hidden: !paymentMessage }">{{ paymentMessage }}</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form {
|
||||
width:100vw;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
/* 其他样式保持不变... */
|
||||
</style>
|
363
app/pages/payment/completePage/index.vue
Normal file
363
app/pages/payment/completePage/index.vue
Normal file
@ -0,0 +1,363 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
const config = useRuntimeConfig()
|
||||
const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY)
|
||||
const statusIcon = ref('')
|
||||
const statusText = ref('')
|
||||
const intentId = ref('')
|
||||
const intentStatus = ref('')
|
||||
const viewDetailsUrl = ref('')
|
||||
const iconColor = ref('')
|
||||
|
||||
const SuccessIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const ErrorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const InfoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
|
||||
<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
function setPaymentDetails(intent) {
|
||||
let status = "Something went wrong, please try again."
|
||||
let color = "#DF1B41"
|
||||
let icon = ErrorIcon
|
||||
|
||||
if (!intent) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
switch (intent.status) {
|
||||
case "succeeded":
|
||||
status = "Payment succeeded"
|
||||
color = "#30B130"
|
||||
icon = SuccessIcon
|
||||
break
|
||||
case "processing":
|
||||
status = "Your payment is processing."
|
||||
color = "#6D6E78"
|
||||
icon = InfoIcon
|
||||
break
|
||||
case "requires_payment_method":
|
||||
status = "Your payment was not successful, please try again."
|
||||
break
|
||||
}
|
||||
|
||||
iconColor.value = color
|
||||
statusIcon.value = icon
|
||||
statusText.value = status
|
||||
intentId.value = intent.id
|
||||
intentStatus.value = intent.status
|
||||
viewDetailsUrl.value = `https://dashboard.stripe.com/payments/${intent.id}`
|
||||
}
|
||||
|
||||
function setErrorState() {
|
||||
iconColor.value = "#DF1B41"
|
||||
statusIcon.value = ErrorIcon
|
||||
statusText.value = "Something went wrong, please try again."
|
||||
}
|
||||
|
||||
async function checkStatus() {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const clientSecret = urlParams.get("payment_intent_client_secret")
|
||||
|
||||
if (!clientSecret) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret)
|
||||
setPaymentDetails(paymentIntent)
|
||||
router.push({
|
||||
path:'/payment/result',
|
||||
query:{
|
||||
orderNo:route.query.orderNo
|
||||
}
|
||||
})
|
||||
}
|
||||
const router=useRouter()
|
||||
const route=useRoute()
|
||||
onMounted(() => {
|
||||
checkStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="payment-status">
|
||||
<div id="status-icon" :style="{ backgroundColor: iconColor }" v-html="statusIcon"></div>
|
||||
<h2 id="status-text">{{ statusText }}</h2>
|
||||
<div id="details-table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="TableLabel">id</td>
|
||||
<td id="intent-id" class="TableContent">{{ intentId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="TableLabel">status</td>
|
||||
<td id="intent-status" class="TableContent">{{ intentStatus }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a :href="viewDetailsUrl" id="view-details" rel="noopener noreferrer" target="_blank">
|
||||
View details
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/>
|
||||
<path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/>
|
||||
</svg>
|
||||
</a>
|
||||
<NuxtLink id="retry-button" to="/checkout">Test another</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Variables */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
|
||||
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -5,7 +5,7 @@ import {goodStore} from "~/stores/goods/index.js";
|
||||
import { showLoadingToast ,closeToast} from 'vant';
|
||||
import {authStore} from "~/stores/auth/index.js";
|
||||
import {message} from "~/components/x-message/useMessage.js";
|
||||
const {checkoutSessionUrl,payment}= authStore()
|
||||
const {checkoutSessionUrl,payment,payUid}= authStore()
|
||||
const payStatus=ref(0)
|
||||
definePageMeta({
|
||||
i18n: 'payment.title'
|
||||
@ -38,11 +38,16 @@ const confirmPay=async ()=>{
|
||||
testReturnHost:window.location.origin,
|
||||
testReturnEndPoint:'/payment/result'
|
||||
})
|
||||
console.log('res',res);
|
||||
if (res.status===0){
|
||||
window.location.href=res.data.checkoutSessionUrl
|
||||
// if (res.status===0){
|
||||
// window.location.href=res.data.checkoutSessionUrl
|
||||
// }
|
||||
checkoutSessionUrl.value=res.data.checkoutSessionUrl
|
||||
payUid.value=res.data.payUid
|
||||
router.push('/payment/checkoutPage')
|
||||
}
|
||||
}
|
||||
|
||||
const handleInput = (e) => {
|
||||
// 只允许数字和小数点,且只保留两位小数
|
||||
const value = e.target.value
|
||||
|
@ -1,24 +1,67 @@
|
||||
<script setup>
|
||||
import {orderQuery} from "~/api/goods/index.js";
|
||||
import { showLoadingToast, closeToast } from 'vant';
|
||||
|
||||
definePageMeta({
|
||||
i18n: 'payment.text1',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const {t} = useI18n();
|
||||
const route = useRoute();
|
||||
const resData = ref({})
|
||||
let timer = null
|
||||
let startTime = Date.now()
|
||||
|
||||
const queryOrder = async () => {
|
||||
showLoadingToast({
|
||||
message: '加载中...',
|
||||
forbidClick: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await orderQuery({
|
||||
orderNo: route.query.orderNo
|
||||
})
|
||||
|
||||
if (res.status === 0) {
|
||||
resData.value = res.data
|
||||
|
||||
// 如果状态为1或者超过5秒,停止轮询
|
||||
if (resData.value.status === 1 || Date.now() - startTime > 5000) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
await queryOrder()
|
||||
|
||||
// 开始轮询
|
||||
timer = setInterval(async () => {
|
||||
await queryOrder()
|
||||
}, 1000)
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
})
|
||||
|
||||
const statusLabel = {
|
||||
1: t('payment.text2'),
|
||||
2: t('payment.text3'),
|
||||
3: t('payment.text4'),
|
||||
4: t('payment.text5'),
|
||||
}
|
||||
|
||||
const goHome = () => {
|
||||
router.push('/')
|
||||
}
|
||||
@ -40,5 +83,4 @@ const goHome=()=>{
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -12,8 +12,9 @@ export const authStore = createGlobalState(() => {
|
||||
buyUid:'',
|
||||
auctionArtworkUuid:''
|
||||
})
|
||||
|
||||
const payUid=useLocalStorage('payUid','')
|
||||
return{
|
||||
payUid,
|
||||
selectedZone,
|
||||
payment,
|
||||
checkoutSessionUrl,
|
||||
|
1
env/.env.test
vendored
1
env/.env.test
vendored
@ -3,3 +3,4 @@ NUXT_PUBLIC_API_BASE=https://auction-test.szjixun.cn
|
||||
NUXT_PUBLIC_API_COLLECT_CODE=http://auction-test.szjixun.cn
|
||||
NUXT_API_SECRET=test-secret
|
||||
NUXT_PUBLIC_SOCKET_URL=wss://auction-test.szjixun.cn
|
||||
NUXT_PUBLIC_PKEY=pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV
|
@ -268,7 +268,9 @@
|
||||
"placeholder": {
|
||||
"amount": "Maximum {currency}{price}"
|
||||
},
|
||||
"amount": "Payment Amount"
|
||||
"amount": "Payment Amount",
|
||||
"amountRequired": "Please enter the amount",
|
||||
"exceedAmount": "Can't be greater than the total amount"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
|
@ -268,7 +268,9 @@
|
||||
"placeholder": {
|
||||
"amount": "最大 {currency}{price}"
|
||||
},
|
||||
"amount": "支払い金額"
|
||||
"amount": "支払い金額",
|
||||
"amountRequired": "金額を入力してください",
|
||||
"exceedAmount": "総額よりも大きくすることはできません"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
|
@ -269,7 +269,9 @@
|
||||
"placeholder": {
|
||||
"amount": "最多"
|
||||
},
|
||||
"amount": "支付金额"
|
||||
"amount": "支付金额",
|
||||
"amountRequired": "请输入金额",
|
||||
"exceedAmount": "不能大于全部金额"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
|
@ -319,7 +319,9 @@
|
||||
"placeholder": {
|
||||
"amount": "最多{currency}{price}"
|
||||
},
|
||||
"amount": "支付金額"
|
||||
"amount": "支付金額",
|
||||
"amountRequired": "請輸入金額",
|
||||
"exceedAmount": "不能大於全部金額"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
|
@ -72,6 +72,13 @@ export default defineNuxtConfig({
|
||||
link: [
|
||||
{ rel: 'icon', href: '/favicon.ico', sizes: 'any' },
|
||||
],
|
||||
// stripe支付CDN引用
|
||||
script: [
|
||||
{
|
||||
src: 'https://js.stripe.com/v3/',
|
||||
defer: true // 可选,建议添加 defer
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover,user-scalable=no' },
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
|
Loading…
Reference in New Issue
Block a user