adata-ui 2.0.39 → 2.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.config.ts CHANGED
@@ -23,6 +23,7 @@ declare module '@nuxt/schema' {
23
23
  counterParty?: string
24
24
  langIsOn?: boolean
25
25
  authMode?: 'local' | 'separate'
26
+ cloudPayments?: string
26
27
  }
27
28
  }
28
29
  }
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { buildLocalizedUrl } from '#adata-ui/utils/localizedNavigation'
3
+ import PaymentProcess from '#adata-ui/components/features/payment/process/PaymentProcess.vue'
4
+
5
+ const { t, locale } = useI18n()
6
+ const { myLayer } = useAppConfig()
7
+
8
+ const { topUpSidePanel } = usePayment()
9
+
10
+ const landingUrl = myLayer.landingUrl
11
+
12
+ function onBuy() {
13
+ topUpSidePanel.value = true
14
+ }
15
+ </script>
16
+
17
+ <template>
18
+ <div class="gradient flex justify-between rounded-lg p-4 ring-1 ring-inset ring-blue-700/80 dark:bg-gray-800">
19
+ <div>
20
+ <p class="mb-2 font-semibold">
21
+ {{ t('payment.banner.title') }}
22
+ </p>
23
+ <i18n-t
24
+ keypath="payment.banner.subtitle"
25
+ tag="p"
26
+ class="text-sm text-gray-600 dark:text-gray-200"
27
+ >
28
+ <template #tariffs>
29
+ <nuxt-link
30
+ class="font-semibold text-blue-700 dark:text-blue-500"
31
+ :to="buildLocalizedUrl(locale, landingUrl, '/tariffs')"
32
+ >
33
+ {{ t('payment.banner.tariffs') }}
34
+ </nuxt-link>
35
+ </template>
36
+ </i18n-t>
37
+ </div>
38
+
39
+ <a-button
40
+ size="md"
41
+ class="self-end"
42
+ @click="onBuy"
43
+ >
44
+ {{ t('actions.buy') }}
45
+ </a-button>
46
+ </div>
47
+
48
+ <payment-process />
49
+ </template>
50
+
51
+ <style scoped>
52
+ .gradient {
53
+ background: linear-gradient(
54
+ 239.71deg,
55
+ #B3D5F9 -7.48%,
56
+ #E6F1FD 39.62%,
57
+ #CCE2FB 85.82%
58
+ );
59
+ }
60
+
61
+ .dark .gradient {
62
+ background: linear-gradient(
63
+ 239.71deg,
64
+ #17303F -7.48%,
65
+ #161617 39.62%,
66
+ #17303F 85.82%
67
+ );
68
+ }
69
+ </style>
@@ -0,0 +1,158 @@
1
+ <script setup lang="ts">
2
+ import QRCode from 'qrcode'
3
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
4
+
5
+ const props = defineProps<{
6
+ requestUrl: string
7
+ requestId: number
8
+ }>()
9
+
10
+ const emit = defineEmits<{
11
+ (e: 'back', type: 'kaspi'): void
12
+ (e: 'updateUserRate'): void
13
+ }>()
14
+
15
+ const isOpen = defineModel({ default: false })
16
+
17
+ const { t, locale } = useI18n()
18
+ const { commonAuth } = useAppConfig()
19
+ const accessToken = useCookie('accessToken')
20
+ const colorMode = useColorMode()
21
+
22
+ const userApiURL = commonAuth.userApiURL
23
+
24
+ const paymentSuccess = ref(false)
25
+ let intervalId: NodeJS.Timeout | null = null
26
+
27
+ function onBack() {
28
+ emit('back', 'kaspi')
29
+ }
30
+
31
+ async function checkPayment() {
32
+ try {
33
+ const { data } = await $fetch(`${removeTrailingSlash(userApiURL)}/user/kaspi-balance/invoice-status`, {
34
+ method: 'GET',
35
+ credentials: 'include',
36
+ headers: {
37
+ Authorization: `Bearer ${accessToken.value}`,
38
+ lang: locale.value,
39
+ },
40
+ params: {
41
+ request_id: props.requestId,
42
+ initial: 0,
43
+ },
44
+ })
45
+
46
+ if (data.status === 'completed') {
47
+ paymentSuccess.value = true
48
+ }
49
+ }
50
+ catch (e) {
51
+ console.error(e)
52
+ }
53
+ }
54
+
55
+ watch(isOpen, async (value) => {
56
+ if (!value) {
57
+ if (intervalId) {
58
+ clearInterval(intervalId)
59
+ }
60
+ return
61
+ }
62
+
63
+ await nextTick()
64
+
65
+ const canvas = document.getElementById('kaspiQr') as HTMLCanvasElement | null
66
+ if (!canvas) return
67
+
68
+ QRCode.toCanvas(
69
+ canvas,
70
+ props.requestUrl,
71
+ {
72
+ errorCorrectionLevel: 'H',
73
+ width: 300,
74
+ margin: 0,
75
+ color: {
76
+ dark: colorMode.value === 'dark' ? '#FFFFFF' : '#000000', // квадраты
77
+ light: colorMode.value === 'dark' ? '#000000' : '#FFFFFF' // фон
78
+ }
79
+ },
80
+ (error) => {
81
+
82
+ if (error) {
83
+ console.error(error)
84
+ return
85
+ }
86
+
87
+ const ctx = canvas.getContext('2d')
88
+ if (!ctx) return
89
+
90
+ const logo = new Image()
91
+ logo.src = '/kaspi/logo.svg'
92
+ logo.onload = () => {
93
+ const size = canvas.width * 0.2
94
+ const x = (canvas.width - size) / 2
95
+ const y = (canvas.height - size) / 2
96
+
97
+ ctx.fillStyle = colorMode.value === 'dark' ? '#000000' : '#fff'
98
+ ctx.fillRect(x - 6, y - 6, size + 12, size + 12)
99
+
100
+ ctx.drawImage(logo, x, y, size, size)
101
+ }
102
+ })
103
+
104
+ intervalId = setInterval(() => {
105
+ if (paymentSuccess.value && intervalId) {
106
+ clearInterval(intervalId)
107
+ emit('updateUserRate')
108
+ return
109
+ }
110
+ checkPayment()
111
+ }, 1000)
112
+ })
113
+
114
+ </script>
115
+
116
+ <template>
117
+ <a-side-panel v-model="isOpen" width="484px">
118
+ <template #header>
119
+ <div class="flex items-center gap-2">
120
+ <button @click="onBack">
121
+ <a-icon-chevron-left class="size-6" />
122
+ </button>
123
+ <p class="text-xl font-semibold">
124
+ {{ t('payment.kaspi.title') }}
125
+ </p>
126
+ </div>
127
+ </template>
128
+
129
+ <div class="flex flex-col gap-6">
130
+ <div class="flex flex-col items-center text-center text-black dark:text-white">
131
+ <p class="mb-4 font-semibold text-sm">
132
+ {{ t('payment.kaspi.description') }}
133
+ </p>
134
+
135
+ <div class="flex gap-3 items-center">
136
+ <a-icon-kaspi-qr class="size-16" />
137
+ <span class="font-bold text-[44px]">
138
+ {{ t('payment.kaspi.qr') }}
139
+ </span>
140
+ </div>
141
+
142
+ <p class="mt-5 font-bold text-2xl">{{ t('payment.kaspi.scan') }}</p>
143
+
144
+ <canvas class="mt-5" id="kaspiQr"></canvas>
145
+
146
+ <p class="mt-10 text-lg">{{ t('payment.kaspi.adata') }}</p>
147
+ </div>
148
+
149
+ <a-button @click="onBack" view="outline">
150
+ {{ t('payment.kaspi.changeMethod') }}
151
+ </a-button>
152
+ </div>
153
+ </a-side-panel>
154
+ </template>
155
+
156
+ <style scoped>
157
+
158
+ </style>
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
+
4
+ const props = defineProps<{
5
+ requestId: number
6
+ }>()
7
+
8
+ const emit = defineEmits<{
9
+ (e: 'back', type: 'kaspi'): void
10
+ (e: 'updateUserRate'): void
11
+ }>()
12
+
13
+ const isOpen = defineModel({ default: false })
14
+
15
+ const { commonAuth } = useAppConfig()
16
+ const { t, locale } = useI18n()
17
+ const localePath = useLocalePath()
18
+ const accessToken = useCookie('accessToken')
19
+
20
+ const userApiURL = commonAuth.userApiURL
21
+
22
+ const paymentSuccess = ref(false)
23
+ let intervalId: NodeJS.Timeout | null = null
24
+
25
+ async function checkPayment() {
26
+ try {
27
+ const { data } = await $fetch(`${removeTrailingSlash(userApiURL)}/user/kaspi-balance/invoice-status`, {
28
+ method: 'GET',
29
+ credentials: 'include',
30
+ headers: {
31
+ Authorization: `Bearer ${accessToken.value}`,
32
+ lang: locale.value,
33
+ },
34
+ params: {
35
+ request_id: props.requestId,
36
+ initial: 0,
37
+ },
38
+ })
39
+
40
+ if (data.status === 'completed') {
41
+ paymentSuccess.value = true
42
+ }
43
+ }
44
+ catch (e) {
45
+ console.error(e)
46
+ }
47
+ }
48
+
49
+ function goKaspi() {
50
+ window.open(localePath(`https://kaspi.kz/pay/adata?service_id=5964&9507=${props.requestId}`), '_blank')
51
+ }
52
+
53
+ function onBack() {
54
+ emit('back', 'kaspi')
55
+ }
56
+
57
+ watch(isOpen, async (value) => {
58
+ if (!value) {
59
+ if (intervalId) {
60
+ clearInterval(intervalId)
61
+ }
62
+ return
63
+ }
64
+
65
+ intervalId = setInterval(() => {
66
+ if (paymentSuccess.value && intervalId) {
67
+ clearInterval(intervalId)
68
+ emit('updateUserRate')
69
+ return
70
+ }
71
+ checkPayment()
72
+ }, 1000)
73
+ })
74
+ </script>
75
+
76
+ <template>
77
+ <a-side-panel
78
+ v-model="isOpen"
79
+ width="484px"
80
+ >
81
+ <div class="flex flex-col gap-5">
82
+ <p class="text-center text-2xl font-bold whitespace-pre-wrap">
83
+ {{ t('payment.redirect.title') }}
84
+ </p>
85
+ <a-button block @click="goKaspi">
86
+ {{ t('payment.redirect.goKaspi') }}
87
+ </a-button>
88
+ <div class="flex flex-col gap-2">
89
+ <p class="text-center text-sm">
90
+ {{ t('payment.redirect.paymentId') }}
91
+ </p>
92
+ <p class="text-center text-xl font-semibold">
93
+ {{ requestId }}
94
+ </p>
95
+ </div>
96
+ <p class="text-center text-sm">
97
+ {{ t('payment.redirect.description') }}
98
+ </p>
99
+ <a-button
100
+ view="outline"
101
+ block
102
+ @click="onBack"
103
+ >
104
+ {{ t('payment.redirect.changeMethod') }}
105
+ </a-button>
106
+ </div>
107
+ </a-side-panel>
108
+ </template>
109
+
110
+ <style scoped>
111
+
112
+ </style>
@@ -0,0 +1,117 @@
1
+ <script setup lang="ts">
2
+ import { AIconPaymentCard, AIconPaymentKaspi } from '#components'
3
+
4
+ type IBank = 'kaspi' | 'other'
5
+
6
+ const props = defineProps<{
7
+ sum: number
8
+ }>()
9
+
10
+ const emit = defineEmits<{
11
+ (e: 'nextBank', bank: IBank, sum: number): void
12
+ (e: 'back', type: 'method'): void
13
+ }>()
14
+
15
+ const isOpen = defineModel({ default: false })
16
+
17
+ const { t } = useI18n()
18
+
19
+ const checkbox = ref<IBank>('kaspi')
20
+ const cards = [
21
+ {
22
+ id: 'kaspi',
23
+ name: 'payment.method.kaspi',
24
+ sum: 10000,
25
+ img: AIconPaymentKaspi,
26
+ },
27
+ {
28
+ id: 'other',
29
+ name: 'payment.method.other',
30
+ sum: 10000,
31
+ img: AIconPaymentCard,
32
+ },
33
+ ]
34
+
35
+ function confirm() {
36
+ emit('nextBank', checkbox.value, props.sum)
37
+ }
38
+
39
+ function cancel() {
40
+ isOpen.value = false
41
+ }
42
+
43
+ function onBack() {
44
+ emit('back', 'method')
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <a-side-panel
50
+ v-model="isOpen"
51
+ width="484px"
52
+ >
53
+ <template #header>
54
+ <div class="flex items-center gap-2">
55
+ <button @click="onBack">
56
+ <a-icon-chevron-left class="size-6" />
57
+ </button>
58
+ <p class="text-xl font-semibold">
59
+ {{ t('payment.method.title') }}
60
+ </p>
61
+ </div>
62
+ </template>
63
+
64
+ <div class="flex flex-col gap-6">
65
+ <div class="flex flex-col items-center gap-4">
66
+ <a-ill-phone-check class="size-[120px]" />
67
+ <p class="text-center text-sm font-semibold">
68
+ {{ t('payment.method.description') }}
69
+ </p>
70
+ </div>
71
+
72
+ <div class="font-semibold">
73
+ {{ t('payment.method.note') }}
74
+ </div>
75
+
76
+ <div class="flex flex-col gap-2">
77
+ <a-radio-button
78
+ v-for="(item) in cards"
79
+ :key="item.id"
80
+ v-model="checkbox"
81
+ :name="`${item.name}`"
82
+ side="right"
83
+ size="lg"
84
+ :value="item.id"
85
+ class="flex items-center justify-between gap-2 rounded-md bg-gray-50 p-[10px_16px] dark:bg-[#393D40]"
86
+ >
87
+ <div class="flex w-full items-center gap-2">
88
+ <component :is="item.img" />
89
+ <div class="flex w-full items-center justify-between">
90
+ {{ t(item.name) }}
91
+ <p class="body-600">
92
+ {{ Math.round(sum).toLocaleString('RU-ru') }} ₸
93
+ </p>
94
+ </div>
95
+ </div>
96
+ </a-radio-button>
97
+ </div>
98
+
99
+ <div class="flex flex-col gap-2 ">
100
+ <a-button block @click="confirm">
101
+ {{ t('actions.topUp') }}
102
+ </a-button>
103
+ <a-button
104
+ view="outline"
105
+ block
106
+ @click="cancel"
107
+ >
108
+ {{ t('actions.close') }}
109
+ </a-button>
110
+ </div>
111
+ </div>
112
+ </a-side-panel>
113
+ </template>
114
+
115
+ <style scoped>
116
+
117
+ </style>
@@ -0,0 +1,136 @@
1
+ <script setup lang="ts">
2
+ import PaymentTopUpSidePanel from '#adata-ui/components/features/payment/process/PaymentTopUpSidePanel.vue'
3
+ import PaymentMethodSidePanel from '#adata-ui/components/features/payment/process/PaymentMethodSidePanel.vue'
4
+ import PaymentKaspiQrSidePanel from '#adata-ui/components/features/payment/process/PaymentKaspiQrSidePanel.vue'
5
+ import PaymentKaspiRedirectSidePanel from '#adata-ui/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue'
6
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
7
+
8
+ const { $toast } = useNuxtApp()
9
+ const { locale } = useI18n()
10
+ const { commonAuth } = useAppConfig()
11
+ const accessToken = useCookie('accessToken')
12
+
13
+ const {
14
+ topUpSidePanel,
15
+ methodSidePanel,
16
+ kaspiQRSidePanel,
17
+ kaspiRedirectSidePanel,
18
+ payByCard
19
+ } = usePayment()
20
+
21
+ const userApiURL = commonAuth.userApiURL
22
+
23
+ const paymentSum = ref(0)
24
+ const requestUrl = ref('')
25
+ const requestId = ref<number | null>(null)
26
+
27
+ function actionPayment(num: number) {
28
+ paymentSum.value = num
29
+ topUpSidePanel.value = false
30
+ methodSidePanel.value = true
31
+ }
32
+
33
+ function toggleKaspiSidePanel(state: boolean) {
34
+ if (window.innerWidth >= 1024) {
35
+ kaspiQRSidePanel.value = state
36
+ }
37
+ else {
38
+ kaspiRedirectSidePanel.value = state
39
+ }
40
+ }
41
+
42
+ function onBack(type: 'method' | 'kaspi') {
43
+ if (type === 'method') {
44
+ topUpSidePanel.value = true
45
+ methodSidePanel.value = false
46
+ }
47
+ else if (type === 'kaspi') {
48
+ toggleKaspiSidePanel(false)
49
+ methodSidePanel.value = true
50
+ }
51
+ }
52
+
53
+ async function directionsBanks(bank: 'kaspi' | 'other', sum: number) {
54
+ methodSidePanel.value = false
55
+ if (bank === 'kaspi') {
56
+ try {
57
+ const { data } = await $fetch(`${removeTrailingSlash(userApiURL)}/user/kaspi-balance/request`, {
58
+ method: 'POST',
59
+ credentials: 'include',
60
+ headers: {
61
+ Authorization: `Bearer ${accessToken.value}`,
62
+ lang: locale.value,
63
+ },
64
+ params: {
65
+ amount: sum,
66
+ initial: 1,
67
+ },
68
+ })
69
+ requestId.value = data.request_id
70
+ requestUrl.value = `https://kaspi.kz/pay/adata?service_id=5964&9507=${data.request_id}`
71
+ toggleKaspiSidePanel(true)
72
+ }
73
+ catch (error) {
74
+ console.error(error)
75
+ $toast.error(error.data.message)
76
+ }
77
+ }
78
+ else if (bank === 'other') {
79
+ await payByCard(sum)
80
+ }
81
+ }
82
+
83
+ async function updateUserRate() {
84
+ try {
85
+ await $fetch(`${removeTrailingSlash(userApiURL)}/user/rate`, {
86
+ method: 'PUT',
87
+ credentials: 'include',
88
+ headers: {
89
+ Authorization: `Bearer ${accessToken.value}`,
90
+ lang: locale.value,
91
+ },
92
+ body: {
93
+ rate_code: 'basic_plus',
94
+ },
95
+ })
96
+ window.location.reload()
97
+ }
98
+ catch (error) {
99
+ $toast.error(error.data.message)
100
+ toggleKaspiSidePanel(false)
101
+ }
102
+ }
103
+ </script>
104
+
105
+ <template>
106
+ <payment-top-up-side-panel
107
+ v-model="topUpSidePanel"
108
+ @next="actionPayment"
109
+ />
110
+
111
+ <payment-method-side-panel
112
+ v-model="methodSidePanel"
113
+ @back="onBack"
114
+ :sum="paymentSum"
115
+ @next-bank="directionsBanks"
116
+ />
117
+
118
+ <payment-kaspi-qr-side-panel
119
+ v-model="kaspiQRSidePanel"
120
+ @back="onBack"
121
+ :request-url="requestUrl"
122
+ :request-id="requestId as number"
123
+ @update-user-rate="updateUserRate"
124
+ />
125
+
126
+ <payment-kaspi-redirect-side-panel
127
+ v-model="kaspiRedirectSidePanel"
128
+ @back="onBack"
129
+ :request-id="requestId as number"
130
+ @update-user-rate="updateUserRate"
131
+ />
132
+ </template>
133
+
134
+ <style scoped>
135
+
136
+ </style>
@@ -0,0 +1,113 @@
1
+ <script setup lang="ts">
2
+ const emit = defineEmits<{
3
+ (e: 'next', sum: number): void
4
+ }>()
5
+
6
+ const isOpen = defineModel({ default: false })
7
+
8
+ const { t } = useI18n()
9
+
10
+ const money = ref(0)
11
+ const sumArr = ref([
12
+ {
13
+ id: 1,
14
+ value: 1000,
15
+ label: '1 000 ₸',
16
+ },
17
+ {
18
+ id: 2,
19
+ value: 5000,
20
+ label: '5 000 ₸',
21
+ },
22
+ {
23
+ id: 3,
24
+ value: 10000,
25
+ label: '10 000 ₸',
26
+ },
27
+ {
28
+ id: 4,
29
+ value: 20000,
30
+ label: '20 000 ₸',
31
+ },
32
+ ])
33
+
34
+ const filterMoney = computed(() => {
35
+ return money.value > 0
36
+ })
37
+
38
+ function onSelectAmount(value: number) {
39
+ money.value = value
40
+ }
41
+
42
+ function onClose() {
43
+ isOpen.value = false
44
+ }
45
+
46
+ function handlePayment() {
47
+ emit('next', money.value)
48
+ }
49
+ </script>
50
+
51
+ <template>
52
+ <a-side-panel v-model="isOpen" width="484px">
53
+ <template #header>
54
+ <div class="text-xl font-semibold">
55
+ {{ t('payment.topUp.title') }}
56
+ </div>
57
+ </template>
58
+
59
+ <div class="flex flex-col gap-6">
60
+ <div class="flex flex-col items-center gap-4 ">
61
+ <a-ill-bill class="size-[120px]" />
62
+ <p class="text-center text-sm font-semibold">
63
+ {{ t('payment.topUp.info') }}
64
+ </p>
65
+ </div>
66
+
67
+ <div class="flex flex-col gap-4">
68
+ <a-input-standard
69
+ v-model="money"
70
+ :label="`${t('payment.topUp.enterAmount')}`"
71
+ type="number"
72
+ pattern="[0-9]*"
73
+ clearable
74
+ />
75
+ <div class="flex gap-4">
76
+ <a-chips
77
+ v-for="item in sumArr"
78
+ :key="item.id"
79
+ view="standard"
80
+ size="lg"
81
+ :class="[
82
+ money === item.value
83
+ ? 'bg-blue-700 text-white hover:bg-blue-700 dark:border-blue-500 dark:bg-transparent dark:text-blue-500 dark:hover:bg-transparent'
84
+ : '',
85
+ ]"
86
+ @click.stop="onSelectAmount(item.value)"
87
+ >
88
+ <span>{{ item.label }}</span>
89
+ </a-chips>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="flex flex-col gap-2">
94
+ <a-button
95
+ :disabled="!filterMoney"
96
+ @click="handlePayment"
97
+ >
98
+ {{ t('actions.topUp') }}
99
+ </a-button>
100
+ <a-button
101
+ view="outline"
102
+ @click="onClose"
103
+ >
104
+ {{ t('actions.back') }}
105
+ </a-button>
106
+ </div>
107
+ </div>
108
+ </a-side-panel>
109
+ </template>
110
+
111
+ <style scoped>
112
+
113
+ </style>