@uappi/public-sdk 1.2.10 → 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';
@@ -77,6 +77,8 @@ O pacote expõe os seguintes escopos, cada um agrupando recursos relacionados:
77
77
  - deleteProductComment: Remove um comentário no produto
78
78
  - createProductComment: Adiciona um comentário no produto
79
79
  - listProductComments: Lista comentários de produto
80
+ - getCategoryTree: Retorna a árvore de uma categoria (atual, filhas, irmãs e pai)
81
+ - listCategoriesTree: Retorna a árvore completa de categorias ativas
80
82
 
81
83
  - [**promotional**](#promotional): Ofertas, cupons, carteiras e pontos promocionais.
82
84
  - listPairOffers: Lista ofertas em pares de um produto (compre junto)
@@ -422,6 +424,14 @@ await api.product.listProductComments(
422
424
  'condition' | 'order' | 'limit' | 'offset' | 'fields'
423
425
  >
424
426
  ): ProductCommentsResponse;
427
+
428
+ // Retorna a árvore de uma categoria: categoria atual, filhas, irmãs e pai.
429
+ await api.product.getCategoryTree(
430
+ categoryId: number
431
+ ): CategoryTree;
432
+
433
+ // Retorna a árvore completa de categorias ativas, com todos os níveis aninhados.
434
+ await api.product.listCategoriesTree(): Array<CategoryTreeItem>;
425
435
  ```
426
436
 
427
437
 
@@ -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>({
@@ -0,0 +1,15 @@
1
+ import { usePublicApi } from '..';
2
+
3
+ import type { CategoryTree, CategoryTreeItem } from '../types';
4
+
5
+ export function getCategoryTree(categoryId: number) {
6
+ return usePublicApi<CategoryTree>({
7
+ endpoint: `/publics/products/categories/${categoryId}/tree`,
8
+ });
9
+ }
10
+
11
+ export function listCategoriesTree() {
12
+ return usePublicApi<Array<CategoryTreeItem>>({
13
+ endpoint: '/publics/products/categories/tree',
14
+ });
15
+ }
@@ -1,5 +1,6 @@
1
1
  import { listBanners } from './endpoints/BannerComposable';
2
2
  import { listBrands } from './endpoints/BrandComposable';
3
+ import { getCategoryTree, listCategoriesTree } from './endpoints/CategoryComposable';
3
4
  import {
4
5
  addProduct,
5
6
  deleteProduct,
@@ -14,7 +15,8 @@ import {
14
15
  buy,
15
16
  applyPoint,
16
17
  deletePoint,
17
- recovery
18
+ recovery,
19
+ paymentToken
18
20
  } from './endpoints/CartComposable';
19
21
  import {
20
22
  listContries,
@@ -155,7 +157,8 @@ const cart = {
155
157
  buy,
156
158
  paymentConditions,
157
159
  upsells,
158
- recovery
160
+ recovery,
161
+ paymentToken
159
162
  };
160
163
 
161
164
  const product = {
@@ -168,6 +171,8 @@ const product = {
168
171
  deleteProductComment,
169
172
  createProductComment,
170
173
  listProductComments,
174
+ getCategoryTree,
175
+ listCategoriesTree,
171
176
  };
172
177
 
173
178
  const common = {
@@ -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
+ }
@@ -0,0 +1,16 @@
1
+ export interface CategoryTreeNode {
2
+ id: number;
3
+ name: string;
4
+ url: string;
5
+ }
6
+
7
+ export interface CategoryTree {
8
+ current: CategoryTreeNode;
9
+ children: Array<CategoryTreeNode>;
10
+ sisters: Array<CategoryTreeNode>;
11
+ parent: Array<CategoryTreeNode>;
12
+ }
13
+
14
+ export interface CategoryTreeItem extends CategoryTreeNode {
15
+ children: Array<CategoryTreeItem>;
16
+ }
@@ -40,11 +40,18 @@ 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,
46
48
  CartErrorCode,
47
49
  } from './endpoints/cart/cart.types';
50
+ import type {
51
+ CategoryTree,
52
+ CategoryTreeItem,
53
+ CategoryTreeNode,
54
+ } from './endpoints/category/category.interfaces';
48
55
  import type { CountryResponse } from './endpoints/common/common.interfaces';
49
56
  import type { ContextType } from './endpoints/context/context.types';
50
57
  import type {
@@ -314,6 +321,11 @@ export type {
314
321
  BrandConditions,
315
322
  BrandOrders,
316
323
 
324
+ // Categorias
325
+ CategoryTree,
326
+ CategoryTreeItem,
327
+ CategoryTreeNode,
328
+
317
329
  // Pagination
318
330
  Pagination,
319
331
 
@@ -409,6 +421,8 @@ export type {
409
421
  AddressToCartResponse,
410
422
  BuyCartRequest,
411
423
  BuyCartResponse,
424
+ PaymentTokenCartRequest,
425
+ PaymentTokenCartResponse,
412
426
 
413
427
  // Payment
414
428
  ConfigurationCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uappi/public-sdk",
3
- "version": "1.2.10",
3
+ "version": "1.4.0",
4
4
  "description": "Pacotes públicos para desenvolvimento do ecossistema Uappi",
5
5
  "main": "main.ts",
6
6
  "scripts": {
@@ -8,8 +8,7 @@
8
8
  "type-check": "tsc --noEmit",
9
9
  "prepush": "npm run lint && npm run type-check",
10
10
  "prepublishOnly": "npm run lint && npm run type-check",
11
- "prepare": "husky",
12
- "publish": "npm publish --access public"
11
+ "prepare": "husky"
13
12
  },
14
13
  "repository": {
15
14
  "type": "git",