This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

210
apps/pay/pages/index.vue Normal file
View File

@@ -0,0 +1,210 @@
<template>
<div class="invoice-form">
<div class="invoice-form__header">
<div>
<h2>Payment details</h2>
<p class="invoice-form__order-id">
# {{ invoice.orderId }}
</p>
</div>
<InvoiceFormTimer
:expiration-time="expirationTime"
/>
</div>
<div class="invoice-form__body">
<Transition
name="shift"
mode="out-in"
>
<Component :is="stepComponent" />
</Transition>
</div>
<p class="invoice-form__network-alert">
Адрес работает только для получения Tether (USDT) в сети TRC20
</p>
</div>
</template>
<script setup lang="ts">
import { useSessionStorage } from '@vueuse/core'
import {
InvoiceFormStepExpired,
InvoiceFormStepPending,
InvoiceFormStepRequisites,
InvoiceFormStepSuccess,
InvoiceFormStepUnderpaid,
InvoiceFormStepWaitingRequisites,
} from '#components'
definePageMeta({
validate: async (route) => {
try {
await $api(`/payments/${route.params.invoiceId}`, {
method: 'get',
})
return true
}
catch {
return false
}
},
})
const route = useRoute()
const payedFlag = useSessionStorage(`invoice-${route.params.invoiceId}`, false, { initOnMounted: true })
interface InvoiceTransfer {
txID: string
recipient: string
sender: string
blockchainCode: string
}
interface PublicInvoice {
id: string
orderId: string
amount: number | string
collectedAmount: number | string
currencyCode: string
expiresAt: string
blockchainCode?: string
availableBlockchains: string[]
walletAddress?: string
transfers?: InvoiceTransfer[]
}
const { data: invoice, refresh } = await useAsyncData<PublicInvoice>('public-invoice', async () => {
const nuxtApp = useNuxtApp()
const invoice = await $api<PublicInvoice>(`/payments/${route.params.invoiceId}`, {
method: 'get',
})
if (new Date(invoice.expiresAt) < new Date() || invoice.walletAddress)
return invoice
try {
const selectedBlockchain = await nuxtApp.runWithContext(async () => {
return $api<Pick<PublicInvoice, 'expiresAt' | 'walletAddress'>>(`/payments/${route.params.invoiceId}`, {
method: 'put',
body: {
blockchainCode: invoice!.availableBlockchains[0],
},
})
})
return {
...invoice,
...selectedBlockchain,
}
}
catch (e) {
console.error(e)
return invoice
}
})
const expirationTime = computed(() => {
return new Date(invoice.value?.expiresAt ?? 0).getTime()
})
const { isPassed } = useTimer(expirationTime)
const stepComponent = computed(() => {
const status = invoice.value?.status
switch (status) {
case 'pending':
return InvoiceFormStepWaitingRequisites
case 'awaiting_payment':
if (!isPassed.value)
return InvoiceFormStepRequisites
else
return InvoiceFormStepPending
case 'completed':
return InvoiceFormStepSuccess
case 'underpaid':
return InvoiceFormStepUnderpaid
case 'overpaid':
return InvoiceFormStepSuccess
// return InvoiceFormStepOverpaid
default:
return InvoiceFormStepExpired
}
})
const intervalId = setInterval(refresh, 5000)
watch(payedFlag, () => {
refresh()
})
onUnmounted(() => {
clearInterval(intervalId)
})
provide('public-invoice', { invoice, expirationTime, payedFlag })
</script>
<style lang="scss">
.invoice-form {
width: 532px;
margin: 0 auto;
&__header {
display: flex;
align-items: flex-end;
justify-content: space-between;
padding: 0 16px;
margin-bottom: 16px;
}
&__order-id {
@include txt-r-sb;
color: $clr-grey-500;
margin-top: 4px;
}
&__body {
background: $clr-white;
border-radius: 12px;
padding: 16px;
text-align: center;
}
&__network-alert {
@include txt-i-m;
text-align: center;
color: $clr-grey-400;
padding: 8px 24px;
margin-top: 10px;
}
[data-status-illustration] {
animation: rotate .8s cubic-bezier(.25,.75,.5,1.25);
}
@keyframes rotate {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(360deg);
}
}
}
</style>