@uappi/public-sdk 1.6.0 → 1.8.0
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
2
|
+
import { ref, shallowRef, computed, onMounted, onUnmounted } from 'vue';
|
|
3
3
|
|
|
4
4
|
import api from '../../modules/Api';
|
|
5
5
|
import { purchaseAction } from '../../modules/Tracking/events/actions/purchase';
|
|
@@ -20,13 +20,16 @@ const paymentToken = ref<string | null>(null);
|
|
|
20
20
|
const loadingToken = ref(true);
|
|
21
21
|
const tokenError = ref<string | null>(null);
|
|
22
22
|
const iframeHeight = ref<number | null>(null);
|
|
23
|
+
// Controla o skeleton: só sai quando o iframe termina de carregar e emite
|
|
24
|
+
// UAPPI_PAYMENT_READY, não apenas quando o token foi obtido.
|
|
25
|
+
const paymentReady = ref(false);
|
|
23
26
|
|
|
24
27
|
// Snapshot do carrinho anterior à compra, usado apenas para disparar o evento
|
|
25
28
|
// `purchase` do GTM quando o `UAPPI_PAYMENT_SUCCESS` chegar. É necessário buscar
|
|
26
29
|
// esse dado antes do pagamento porque, após o sucesso, o carrinho já foi
|
|
27
30
|
// consumido pelo `/cart/buy` (chamado internamente pelo iframe) e não reflete
|
|
28
31
|
// mais os itens/frete/descontos da compra que acabou de ser feita.
|
|
29
|
-
const cartSnapshot =
|
|
32
|
+
const cartSnapshot = shallowRef<CartResponse | null>(null);
|
|
30
33
|
|
|
31
34
|
const iframeSrc = computed(() => {
|
|
32
35
|
if (!paymentToken.value) return '';
|
|
@@ -46,20 +49,24 @@ const iframeSrc = computed(() => {
|
|
|
46
49
|
return url.toString();
|
|
47
50
|
});
|
|
48
51
|
|
|
52
|
+
async function requestPaymentToken(): Promise<string> {
|
|
53
|
+
const response = await api.cart.paymentToken({ paymentOptionId: props.option.id });
|
|
54
|
+
|
|
55
|
+
if (!response?.token) {
|
|
56
|
+
console.error('[PaymentHosted] Token ausente na resposta:', response);
|
|
57
|
+
throw new Error('UAPPI_PAYMENT_TOKEN_MISSING');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return response.token;
|
|
61
|
+
}
|
|
62
|
+
|
|
49
63
|
async function fetchPaymentToken(): Promise<void> {
|
|
50
64
|
loadingToken.value = true;
|
|
51
65
|
tokenError.value = null;
|
|
66
|
+
paymentReady.value = false;
|
|
52
67
|
|
|
53
68
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!response?.token) {
|
|
57
|
-
console.error('[PaymentHosted] Token ausente na resposta:', response);
|
|
58
|
-
tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
paymentToken.value = response.token;
|
|
69
|
+
paymentToken.value = await requestPaymentToken();
|
|
63
70
|
} catch (err) {
|
|
64
71
|
console.error('[PaymentHosted] Erro ao buscar token de pagamento:', err);
|
|
65
72
|
tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
|
|
@@ -68,6 +75,25 @@ async function fetchPaymentToken(): Promise<void> {
|
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Chamado quando o iframe avisa (UAPPI_PAYMENT_TOKEN_EXPIRING) que o token atual
|
|
80
|
+
* está perto de expirar. Busca um novo token e o injeta direto no formulário via
|
|
81
|
+
* postMessage (UAPPI_UPDATE_PAYMENT_TOKEN), sem trocar o `src` do iframe — preserva
|
|
82
|
+
* os dados que o usuário já preencheu no cartão.
|
|
83
|
+
*/
|
|
84
|
+
async function refreshPaymentToken(): Promise<void> {
|
|
85
|
+
if (!iframeEl.value?.contentWindow) return;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const token = await requestPaymentToken();
|
|
89
|
+
const targetOrigin = new URL(iframeSrc.value).origin;
|
|
90
|
+
|
|
91
|
+
iframeEl.value.contentWindow.postMessage({ type: 'UAPPI_UPDATE_PAYMENT_TOKEN', token }, targetOrigin);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error('[PaymentHosted] Erro ao renovar token de pagamento:', err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
async function fetchCartSnapshot(): Promise<void> {
|
|
72
98
|
try {
|
|
73
99
|
cartSnapshot.value = await api.cart.listDetail();
|
|
@@ -95,9 +121,14 @@ function handleIframeMessage(event: MessageEvent): void {
|
|
|
95
121
|
|
|
96
122
|
switch (type) {
|
|
97
123
|
case 'UAPPI_PAYMENT_READY':
|
|
124
|
+
paymentReady.value = true;
|
|
98
125
|
emit('payment-ready');
|
|
99
126
|
break;
|
|
100
127
|
|
|
128
|
+
case 'UAPPI_PAYMENT_TOKEN_EXPIRING':
|
|
129
|
+
refreshPaymentToken();
|
|
130
|
+
break;
|
|
131
|
+
|
|
101
132
|
case 'UAPPI_PAYMENT_SUCCESS':
|
|
102
133
|
if (cartSnapshot.value) {
|
|
103
134
|
purchaseAction(props.option.paymentType.code, cartSnapshot.value);
|
|
@@ -134,23 +165,25 @@ onUnmounted(() => {
|
|
|
134
165
|
|
|
135
166
|
<template>
|
|
136
167
|
<div class="payment-hosted">
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
<p v-else-if="tokenError" class="payment-hosted__error">
|
|
168
|
+
<p v-if="tokenError" class="payment-hosted__error">
|
|
140
169
|
{{ tokenError }}
|
|
141
170
|
</p>
|
|
142
171
|
|
|
143
|
-
<
|
|
144
|
-
v-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
172
|
+
<div v-else class="payment-hosted__stage">
|
|
173
|
+
<div v-if="!paymentReady" class="payment-hosted__skeleton" />
|
|
174
|
+
|
|
175
|
+
<iframe
|
|
176
|
+
v-if="iframeSrc"
|
|
177
|
+
ref="iframeEl"
|
|
178
|
+
:src="iframeSrc"
|
|
179
|
+
:style="iframeHeight ? { height: `${iframeHeight}px` } : undefined"
|
|
180
|
+
:class="['payment-hosted__iframe', { 'payment-hosted__iframe--hidden': !paymentReady }]"
|
|
181
|
+
frameborder="0"
|
|
182
|
+
scrolling="no"
|
|
183
|
+
title="Formulário de pagamento seguro"
|
|
184
|
+
allow="payment"
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
154
187
|
</div>
|
|
155
188
|
</template>
|
|
156
189
|
|
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
width: 100%;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
.payment-hosted__stage {
|
|
6
|
+
position: relative;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
.payment-hosted__iframe {
|
|
6
11
|
display: block;
|
|
7
12
|
width: 100%;
|
|
8
|
-
min-height: 420px;
|
|
9
13
|
border: none;
|
|
10
14
|
transition: height 0.2s ease;
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
.payment-hosted__iframe--hidden {
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
opacity: 0;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
.payment-hosted__skeleton {
|
|
14
26
|
width: 100%;
|
|
15
27
|
height: 420px;
|