@uappi/public-sdk 1.3.0 → 1.4.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.
@@ -0,0 +1,127 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
3
+
4
+ import api from '../../modules/Api';
5
+
6
+ import type { Props, Emits, PaymentSuccessData, PaymentRedirectData } from './types';
7
+
8
+ // `PaymentHostedFields` renderiza o formulário de pagamento via iframe isolado,
9
+ // servido pelo serviço `render.uappi`. Todos os dados sensíveis (número do cartão,
10
+ // CVV, etc.) trafegam diretamente entre o iframe e o gateway, sem passar pela loja.
11
+ const props = defineProps<Props>();
12
+
13
+ const emit = defineEmits<Emits>();
14
+
15
+ const iframeEl = ref<HTMLIFrameElement | null>(null);
16
+
17
+ const paymentToken = ref<string | null>(null);
18
+ const loadingToken = ref(true);
19
+ const tokenError = ref<string | null>(null);
20
+
21
+ const iframeSrc = computed(() => {
22
+ if (!paymentToken.value) return '';
23
+
24
+ const host = props.iframeHost
25
+ ?? (import.meta.env.VITE_PAYMENT_IFRAME_HOST as string | undefined)
26
+ ?? 'https://payments.uappi.store';
27
+
28
+ const url = new URL(host);
29
+
30
+ url.searchParams.set('token', paymentToken.value);
31
+
32
+ if (props.ctaColor) url.searchParams.set('ctaColor', props.ctaColor.replace('#', ''));
33
+ if (props.lang) url.searchParams.set('lang', props.lang);
34
+ if (props.countryCode) url.searchParams.set('countryCode', props.countryCode);
35
+
36
+ return url.toString();
37
+ });
38
+
39
+ async function fetchPaymentToken(): Promise<void> {
40
+ loadingToken.value = true;
41
+ tokenError.value = null;
42
+
43
+ try {
44
+ const response = await api.cart.paymentToken({ paymentOptionId: props.option.id });
45
+
46
+ if (!response?.token) {
47
+ console.error('[PaymentHostedFields] Token ausente na resposta:', response);
48
+ tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
49
+ return;
50
+ }
51
+
52
+ paymentToken.value = response.token;
53
+ } catch (err) {
54
+ console.error('[PaymentHostedFields] Erro ao buscar token de pagamento:', err);
55
+ tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
56
+ } finally {
57
+ loadingToken.value = false;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Só processa mensagens cuja origem seja a própria janela do iframe renderizado
63
+ * por este componente, evitando que outra janela/frame na página finja ser o
64
+ * render.uappi e injete eventos de pagamento falsos.
65
+ */
66
+ function handleIframeMessage(event: MessageEvent): void {
67
+ if (!iframeEl.value || event.source !== iframeEl.value.contentWindow) return;
68
+
69
+ const { type, data, url, errorCode, message } = (event.data ?? {}) as {
70
+ type: string;
71
+ data: PaymentSuccessData & PaymentRedirectData;
72
+ url: string;
73
+ errorCode: string | undefined;
74
+ message: string;
75
+ };
76
+
77
+ switch (type) {
78
+ case 'UAPPI_PAYMENT_READY':
79
+ emit('payment-ready');
80
+ break;
81
+
82
+ case 'UAPPI_PAYMENT_SUCCESS':
83
+ emit('payment-success', data);
84
+ break;
85
+
86
+ case 'UAPPI_PAYMENT_REDIRECT':
87
+ emit('payment-redirect', url, data);
88
+ break;
89
+
90
+ case 'UAPPI_PAYMENT_ERROR':
91
+ emit('payment-error', errorCode, message);
92
+ break;
93
+ }
94
+ }
95
+
96
+ onMounted(async () => {
97
+ window.addEventListener('message', handleIframeMessage);
98
+ await fetchPaymentToken();
99
+ });
100
+
101
+ onUnmounted(() => {
102
+ window.removeEventListener('message', handleIframeMessage);
103
+ });
104
+ </script>
105
+
106
+ <template>
107
+ <div class="payment-hosted-fields">
108
+ <div v-if="loadingToken" class="payment-hosted-fields__skeleton" />
109
+
110
+ <p v-else-if="tokenError" class="payment-hosted-fields__error">
111
+ {{ tokenError }}
112
+ </p>
113
+
114
+ <iframe
115
+ v-else-if="iframeSrc"
116
+ ref="iframeEl"
117
+ :src="iframeSrc"
118
+ class="payment-hosted-fields__iframe"
119
+ frameborder="0"
120
+ scrolling="no"
121
+ title="Formulário de pagamento seguro"
122
+ allow="payment"
123
+ />
124
+ </div>
125
+ </template>
126
+
127
+ <style scoped src="./styles.scoped.css" />
@@ -0,0 +1 @@
1
+ export { default as PaymentHostedFields } from './PaymentHostedFields.vue';
@@ -0,0 +1,37 @@
1
+ .payment-hosted-fields {
2
+ width: 100%;
3
+ }
4
+
5
+ .payment-hosted-fields__iframe {
6
+ display: block;
7
+ width: 100%;
8
+ min-height: 420px;
9
+ border: none;
10
+ }
11
+
12
+ .payment-hosted-fields__skeleton {
13
+ width: 100%;
14
+ height: 420px;
15
+ border-radius: 4px;
16
+ background: linear-gradient(
17
+ 90deg,
18
+ var(--neutral-surface-low) 25%,
19
+ var(--neutral-border) 37%,
20
+ var(--neutral-surface-low) 63%
21
+ );
22
+ background-size: 400% 100%;
23
+ animation: payment-hosted-fields-skeleton 1.4s ease infinite;
24
+ }
25
+
26
+ @keyframes payment-hosted-fields-skeleton {
27
+ 0% { background-position: 100% 50%; }
28
+ 100% { background-position: 0 50%; }
29
+ }
30
+
31
+ .payment-hosted-fields__error {
32
+ margin: 0;
33
+ padding: 16px;
34
+ text-align: center;
35
+ font: var(--body);
36
+ color: var(--negative-text, #d32f2f);
37
+ }
@@ -0,0 +1,33 @@
1
+ import type { PaymentOption, BuyCartResponse } from '../../modules/Api/types';
2
+
3
+ export interface Props {
4
+ /** Opção de pagamento selecionada (ex: item retornado por `api.cart.listPayments`). */
5
+ option: PaymentOption;
6
+ /** Cor hex do botão CTA do iframe (com ou sem `#`). Se omitida, o iframe usa a cor padrão do render.uappi. */
7
+ ctaColor?: string;
8
+ /** Locale do formulário (`pt-BR`, `en-US`, `es-ES`, ...). Padrão do iframe: `pt-BR`. */
9
+ lang?: string;
10
+ /** Código Alpha-3 para pré-selecionar o DDI do telefone (`BRA`, `USA`, ...). Padrão do iframe: `BRA`. */
11
+ countryCode?: string;
12
+ /** Host do serviço render.uappi. Sobrescreve `VITE_PAYMENT_IFRAME_HOST` quando informado. */
13
+ iframeHost?: string;
14
+ }
15
+
16
+ export interface Emits {
17
+ (e: 'payment-ready'): void;
18
+ (e: 'payment-success', data: PaymentSuccessData): void;
19
+ (e: 'payment-redirect', url: string, data: PaymentRedirectData): void;
20
+ (e: 'payment-error', errorCode: string | undefined, message: string): void;
21
+ }
22
+
23
+ /**
24
+ * Dados do pedido criado, repassados pelo iframe em `UAPPI_PAYMENT_SUCCESS`.
25
+ * Mesmo formato retornado por `api.cart.buy` (`POST /cart/buy`).
26
+ */
27
+ export type PaymentSuccessData = BuyCartResponse;
28
+
29
+ /**
30
+ * Dados que acompanham um `UAPPI_PAYMENT_REDIRECT`. O contrato exato é definido
31
+ * pelo serviço render.uappi e pode variar por gateway, por isso não é tipado em detalhe.
32
+ */
33
+ export type PaymentRedirectData = Record<string, unknown>;
@@ -1,3 +1,4 @@
1
1
  export * from './Animations';
2
2
  export * from './ColorTheme';
3
+ export * from './PaymentHostedFields';
3
4
  export * from './SocialAuth';
@@ -10,6 +10,8 @@ import type {
10
10
  PaymentResponse,
11
11
  BuyCartRequest,
12
12
  BuyCartResponse,
13
+ PaymentTokenCartRequest,
14
+ PaymentTokenCartResponse,
13
15
  } from '../types';
14
16
 
15
17
  export function listDetail() {
@@ -111,6 +113,14 @@ export function buy(payload: BuyCartRequest) {
111
113
  });
112
114
  }
113
115
 
116
+ export function paymentToken(payload: PaymentTokenCartRequest) {
117
+ return usePublicApi<PaymentTokenCartResponse>({
118
+ endpoint: '/cart/payment-token',
119
+ method: 'POST',
120
+ payload,
121
+ });
122
+ }
123
+
114
124
 
115
125
  export function recovery(recoveryCode: string) {
116
126
  const response = usePublicApi<CartResponse>({
@@ -15,7 +15,8 @@ import {
15
15
  buy,
16
16
  applyPoint,
17
17
  deletePoint,
18
- recovery
18
+ recovery,
19
+ paymentToken
19
20
  } from './endpoints/CartComposable';
20
21
  import {
21
22
  listContries,
@@ -156,7 +157,8 @@ const cart = {
156
157
  buy,
157
158
  paymentConditions,
158
159
  upsells,
159
- recovery
160
+ recovery,
161
+ paymentToken
160
162
  };
161
163
 
162
164
  const product = {
@@ -159,3 +159,11 @@ export interface BuyCartResponse {
159
159
  userId: null;
160
160
  userName: null;
161
161
  }
162
+
163
+ export interface PaymentTokenCartRequest {
164
+ paymentOptionId: number;
165
+ }
166
+
167
+ export interface PaymentTokenCartResponse {
168
+ token: string;
169
+ }
@@ -40,6 +40,8 @@ import type {
40
40
  AddressToCartResponse,
41
41
  BuyCartRequest,
42
42
  BuyCartResponse,
43
+ PaymentTokenCartRequest,
44
+ PaymentTokenCartResponse,
43
45
  } from './endpoints/cart/cart.interfaces';
44
46
  import type {
45
47
  AddressToCartType,
@@ -419,6 +421,8 @@ export type {
419
421
  AddressToCartResponse,
420
422
  BuyCartRequest,
421
423
  BuyCartResponse,
424
+ PaymentTokenCartRequest,
425
+ PaymentTokenCartResponse,
422
426
 
423
427
  // Payment
424
428
  ConfigurationCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uappi/public-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Pacotes públicos para desenvolvimento do ecossistema Uappi",
5
5
  "main": "main.ts",
6
6
  "scripts": {