@uappi/public-sdk 1.5.0 → 1.7.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.
- package/components/PaymentHosted/PaymentHosted.vue +58 -25
- package/components/PaymentHosted/styles.scoped.css +13 -0
- package/modules/Api/endpoints/PromotionalForecastComposable.ts +11 -0
- package/modules/Api/index.ts +2 -0
- package/modules/Api/types/context/api-params.types.ts +3 -0
- package/modules/Api/types/endpoints/promotional-forecast/promotional-forecast.interfaces.ts +11 -0
- package/modules/Api/types/index.ts +8 -0
- package/package.json +1 -1
|
@@ -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,6 +2,11 @@
|
|
|
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%;
|
|
@@ -10,6 +15,14 @@
|
|
|
10
15
|
transition: height 0.2s ease;
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
.payment-hosted__iframe--hidden {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
opacity: 0;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
.payment-hosted__skeleton {
|
|
14
27
|
width: 100%;
|
|
15
28
|
height: 420px;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { usePublicApi } from '..';
|
|
2
|
+
|
|
3
|
+
import type { PromotionalForecastResponse } from '../types';
|
|
4
|
+
|
|
5
|
+
export function getPromotionalForecast(productId: number) {
|
|
6
|
+
return usePublicApi<PromotionalForecastResponse>({
|
|
7
|
+
endpoint: '/publics/products/promotional-forecast',
|
|
8
|
+
method: 'GET',
|
|
9
|
+
params: { productId },
|
|
10
|
+
});
|
|
11
|
+
}
|
package/modules/Api/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { createInterest } from './endpoints/InterestComposable';
|
|
|
56
56
|
import { loggout } from './endpoints/LoggoutComposable';
|
|
57
57
|
import { listMenu } from './endpoints/MenuComposable';
|
|
58
58
|
import { listPairOffers } from './endpoints/PairOfferComposable';
|
|
59
|
+
import { getPromotionalForecast } from './endpoints/PromotionalForecastComposable';
|
|
59
60
|
import { paymentConditions } from './endpoints/PaymentComposable';
|
|
60
61
|
import {
|
|
61
62
|
deleteProductComment,
|
|
@@ -190,6 +191,7 @@ const promotional = {
|
|
|
190
191
|
deleteWallet,
|
|
191
192
|
applyPoint,
|
|
192
193
|
deletePoint,
|
|
194
|
+
getPromotionalForecast,
|
|
193
195
|
};
|
|
194
196
|
|
|
195
197
|
const notify = {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PromotionalForecastPointSystem {
|
|
2
|
+
name: string;
|
|
3
|
+
pointValue: number;
|
|
4
|
+
finalAmount: number;
|
|
5
|
+
finalValue: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PromotionalForecastResponse {
|
|
9
|
+
pointSystem: PromotionalForecastPointSystem | null;
|
|
10
|
+
promotions: Array<Record<string, unknown>>;
|
|
11
|
+
}
|
|
@@ -143,6 +143,10 @@ import type {
|
|
|
143
143
|
PairOffer,
|
|
144
144
|
PairOfferResponse,
|
|
145
145
|
} from './endpoints/pair-offer/pair-offer.interfaces';
|
|
146
|
+
import type {
|
|
147
|
+
PromotionalForecastPointSystem,
|
|
148
|
+
PromotionalForecastResponse,
|
|
149
|
+
} from './endpoints/promotional-forecast/promotional-forecast.interfaces';
|
|
146
150
|
import type {
|
|
147
151
|
Payment,
|
|
148
152
|
PaymentConfiguration,
|
|
@@ -354,6 +358,10 @@ export type {
|
|
|
354
358
|
PairOffer,
|
|
355
359
|
PairOfferResponse,
|
|
356
360
|
|
|
361
|
+
// Previsão de promocional/pontos por produto
|
|
362
|
+
PromotionalForecastPointSystem,
|
|
363
|
+
PromotionalForecastResponse,
|
|
364
|
+
|
|
357
365
|
// Avaliaçoes
|
|
358
366
|
ProductReviewRequest,
|
|
359
367
|
CreateProductReviewResponse,
|