@uappi/public-sdk 1.4.2 → 1.5.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.
@@ -2,8 +2,10 @@
2
2
  import { ref, computed, onMounted, onUnmounted } from 'vue';
3
3
 
4
4
  import api from '../../modules/Api';
5
+ import { purchaseAction } from '../../modules/Tracking/events/actions/purchase';
5
6
 
6
7
  import type { Props, Emits, PaymentSuccessData, PaymentRedirectData } from './types';
8
+ import type { CartResponse } from '../../modules/Api/types';
7
9
 
8
10
  // `PaymentHosted` renderiza o formulário de pagamento via iframe isolado,
9
11
  // servido pelo serviço `render.uappi`. Todos os dados sensíveis (número do cartão,
@@ -17,6 +19,14 @@ const iframeEl = ref<HTMLIFrameElement | null>(null);
17
19
  const paymentToken = ref<string | null>(null);
18
20
  const loadingToken = ref(true);
19
21
  const tokenError = ref<string | null>(null);
22
+ const iframeHeight = ref<number | null>(null);
23
+
24
+ // Snapshot do carrinho anterior à compra, usado apenas para disparar o evento
25
+ // `purchase` do GTM quando o `UAPPI_PAYMENT_SUCCESS` chegar. É necessário buscar
26
+ // esse dado antes do pagamento porque, após o sucesso, o carrinho já foi
27
+ // consumido pelo `/cart/buy` (chamado internamente pelo iframe) e não reflete
28
+ // mais os itens/frete/descontos da compra que acabou de ser feita.
29
+ const cartSnapshot = ref<CartResponse | null>(null);
20
30
 
21
31
  const iframeSrc = computed(() => {
22
32
  if (!paymentToken.value) return '';
@@ -58,6 +68,14 @@ async function fetchPaymentToken(): Promise<void> {
58
68
  }
59
69
  }
60
70
 
71
+ async function fetchCartSnapshot(): Promise<void> {
72
+ try {
73
+ cartSnapshot.value = await api.cart.listDetail();
74
+ } catch (err) {
75
+ console.error('[PaymentHosted] Erro ao buscar carrinho para tracking de compra:', err);
76
+ }
77
+ }
78
+
61
79
  /**
62
80
  * Só processa mensagens cuja origem seja a própria janela do iframe renderizado
63
81
  * por este componente, evitando que outra janela/frame na página finja ser o
@@ -66,12 +84,13 @@ async function fetchPaymentToken(): Promise<void> {
66
84
  function handleIframeMessage(event: MessageEvent): void {
67
85
  if (!iframeEl.value || event.source !== iframeEl.value.contentWindow) return;
68
86
 
69
- const { type, data, url, errorCode, message } = (event.data ?? {}) as {
87
+ const { type, data, url, errorCode, message, height } = (event.data ?? {}) as {
70
88
  type: string;
71
89
  data: PaymentSuccessData & PaymentRedirectData;
72
90
  url: string;
73
91
  errorCode: string | undefined;
74
92
  message: string;
93
+ height: number | undefined;
75
94
  };
76
95
 
77
96
  switch (type) {
@@ -80,6 +99,11 @@ function handleIframeMessage(event: MessageEvent): void {
80
99
  break;
81
100
 
82
101
  case 'UAPPI_PAYMENT_SUCCESS':
102
+ if (cartSnapshot.value) {
103
+ purchaseAction(props.option.paymentType.code, cartSnapshot.value);
104
+ } else {
105
+ console.error('[PaymentHosted] Carrinho indisponível: evento de tracking de compra (purchase) não foi disparado.');
106
+ }
83
107
  emit('payment-success', data);
84
108
  break;
85
109
 
@@ -90,24 +114,29 @@ function handleIframeMessage(event: MessageEvent): void {
90
114
  case 'UAPPI_PAYMENT_ERROR':
91
115
  emit('payment-error', errorCode, message);
92
116
  break;
117
+
118
+ case 'UAPPI_PAYMENT_RESIZE':
119
+ if (typeof height === 'number' && height > 0) iframeHeight.value = height;
120
+ break;
93
121
  }
94
122
  }
95
123
 
96
124
  onMounted(async () => {
97
125
  window.addEventListener('message', handleIframeMessage);
98
- await fetchPaymentToken();
126
+ await Promise.all([fetchPaymentToken(), fetchCartSnapshot()]);
99
127
  });
100
128
 
101
129
  onUnmounted(() => {
102
130
  window.removeEventListener('message', handleIframeMessage);
103
131
  });
132
+
104
133
  </script>
105
134
 
106
135
  <template>
107
- <div class="payment-hosted-fields">
108
- <div v-if="loadingToken" class="payment-hosted-fields__skeleton" />
136
+ <div class="payment-hosted">
137
+ <div v-if="loadingToken" class="payment-hosted__skeleton" />
109
138
 
110
- <p v-else-if="tokenError" class="payment-hosted-fields__error">
139
+ <p v-else-if="tokenError" class="payment-hosted__error">
111
140
  {{ tokenError }}
112
141
  </p>
113
142
 
@@ -115,7 +144,8 @@ onUnmounted(() => {
115
144
  v-else-if="iframeSrc"
116
145
  ref="iframeEl"
117
146
  :src="iframeSrc"
118
- class="payment-hosted-fields__iframe"
147
+ :style="iframeHeight ? { height: `${iframeHeight}px` } : undefined"
148
+ class="payment-hosted__iframe"
119
149
  frameborder="0"
120
150
  scrolling="no"
121
151
  title="Formulário de pagamento seguro"
@@ -1,16 +1,16 @@
1
- .payment-hosted-fields {
1
+ .payment-hosted {
2
2
  width: 100%;
3
3
  }
4
4
 
5
- .payment-hosted-fields__iframe {
5
+ .payment-hosted__iframe {
6
6
  display: block;
7
7
  width: 100%;
8
- height: 100%;
9
8
  min-height: 420px;
10
9
  border: none;
10
+ transition: height 0.2s ease;
11
11
  }
12
12
 
13
- .payment-hosted-fields__skeleton {
13
+ .payment-hosted__skeleton {
14
14
  width: 100%;
15
15
  height: 420px;
16
16
  border-radius: 4px;
@@ -21,15 +21,15 @@
21
21
  var(--neutral-surface-low) 63%
22
22
  );
23
23
  background-size: 400% 100%;
24
- animation: payment-hosted-fields-skeleton 1.4s ease infinite;
24
+ animation: payment-hosted-skeleton 1.4s ease infinite;
25
25
  }
26
26
 
27
- @keyframes payment-hosted-fields-skeleton {
27
+ @keyframes payment-hosted-skeleton {
28
28
  0% { background-position: 100% 50%; }
29
29
  100% { background-position: 0 50%; }
30
30
  }
31
31
 
32
- .payment-hosted-fields__error {
32
+ .payment-hosted__error {
33
33
  margin: 0;
34
34
  padding: 16px;
35
35
  text-align: center;
@@ -25,6 +25,8 @@ Renderiza o formulário de pagamento (cartão, etc.) dentro de um `<iframe>` iso
25
25
 
26
26
  O componente busca um token de pagamento via `api.cart.paymentToken` ao montar, monta a URL do iframe com esse token, e escuta `postMessage` do iframe para emitir eventos de pagamento. Mensagens de janelas que não sejam o `contentWindow` do próprio iframe são ignoradas.
27
27
 
28
+ A altura do iframe tem um piso definido em CSS (`min-height`), mas se ajusta automaticamente ao conteúdo quando o `render.uappi` envia `postMessage({ type: 'UAPPI_PAYMENT_RESIZE', height })` (`height` em pixels, número do conteúdo real do formulário). Isso é necessário porque o iframe é cross-origin — o componente não tem acesso ao `contentDocument` para medir a altura sozinho. Sem esse evento, o iframe mantém apenas o `min-height` fixo.
29
+
28
30
  ```vue
29
31
  <script setup lang="ts">
30
32
  import { PaymentHosted } from '@uappi/public-sdk';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uappi/public-sdk",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Pacotes públicos para desenvolvimento do ecossistema Uappi",
5
5
  "main": "main.ts",
6
6
  "scripts": {